c# - 像在 C# linq 中一样在 Java 8 中获取匿名对象

标签 c# lambda java-8

假设我有一个 Person 类,其中包含 FirstName、LastName、Age、Salary 等字段。现在我在 C# linq 中编写了这段代码,其中 persons 是一个列表。

var lstFirstAndLastNamesOnly = persons.Where(x => x.Age > 35).Select(x => new {x.FirstName, x.LastName}).ToList();

这将使我得到一个具有 FirstName 和 LastName 的匿名类型。我如何在 Java 8 中写这样的东西?什么可能是更好的方法,以便当我在类里面说 100 个字段时只返回少数字段。

最佳答案

嗯,Java 编程语言有匿名类型,但它们没有 C# 示例中的那么有用。即,以下将起作用:

persons.stream().filter(x -> x.age > 35)
    .map(x -> new Object(){ String first = x.firstName, last = x.lastName; })
    .forEach(name -> System.out.println(name.first + " " + name.last));

您会注意到,匿名类型的声明仍然需要类型规范,这将是匿名类型的父类(super class)型。此外,您必须正式声明持有数据的成员的类型和名称。

在 JDK 10 之前,lambda 表达式是唯一可以在不指定变量类型并让编译器推断的情况下声明变量(参数)的地方。

从 JDK 10 开始,您可以使用 var 关键字来声明其类型从其初始值设定项推断的局部变量,即使它是不可表示的类型也是如此。

var name = persons.stream().filter(x -> x.age > 35)
    .map(x -> new Object(){ String first = x.firstName, last = x.lastName; })
    .orElse(null);
if(name != null) System.out.println(name.first + " " + name.last);

您可以在流畅的上下文中访问它,而无需变量,这也适用于 JDK 10 之前的版本,例如

System.out.println(
    persons.stream().filter(x -> x.age > 35)
        .map(x -> new Object(){ String a = x.firstName, b = x.lastName; })
        .findFirst().get().a
);

但是因为这只允许访问单个成员,所以它没什么用,因为如果您只需要一个成员,您可以首先映射到成员值。

在这个具体示例中,您可以使用一个包含两个元素的 String 数组:

List<String[]> lstFirstAndLastNamesOnly = persons.stream()
    .filter(x -> x.age > 35)
    .map(x -> new String[] { x.firstName, x.lastName })
    .collect(Collectors.toList());
lstFirstAndLastNamesOnly
    .forEach(name -> System.out.println(name[0]+" "+name[1]));

但是,当然,它们不能替代真正的元组,尤其是当您具有异构类型的元素时。


应该注意的是,这样的匿名类型仍然是一种不同的类型,即在不同代码位置创建的具有相同属性的另一种匿名类型仍然是一种不同的类型。事实上,即使具有相同属性值的实例也不相等,只要您不添加适当的 equals 方法即可。

从 JDK 16 开始,您可以使用 record 来达到这个目的。它不是匿名类,而是用于创建包含某些属性的临时类型的最佳工具。它仍然是一个独特的类型,但它可以在本地声明¹并自动获得有用的 equalshashCodetoString 实现。

var listFirstAndLastNamesOnly = persons.stream()
    .filter(x -> x.age > 35)
    .map(x -> {
        record Name(String firstName, String lastName) {}
        return new Name(x.firstName, x.lastName);
    })
    .toList();

// utilize generated toString() method
listFirstAndLastNamesOnly.forEach(System.out::println);

// or
listFirstAndLastNamesOnly
    .forEach(name -> System.out.println(name.firstName()+" "+name.lastName()));

请注意,这再次使用 var 作为变量声明,因为在映射函数内声明的 Name 记录不在范围内。同样,传递给 forEach 的 lambda 表达式可以推断出正确的元素类型。由于这依赖于局部变量的特性,因此它仅在整个代码都在一个方法中时才有效。

¹ 甚至比匿名类更好,因为 record 不会从周围的上下文中捕获 this 引用

关于c# - 像在 C# linq 中一样在 Java 8 中获取匿名对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41202893/

相关文章:

lambda - 我不明白为什么 lambda 方案有效

java - 使用 Java 8 中的泛型方法对集合进行排序

c# - 如果我的应用程序中需要有 60 个图像控件,我是否必须有 60 个这样巨大的代码块?

c# - Visual Studio 是否优化传递引用?

c# - 如何使用 LINQ 使用条件按子字符串对文件名进行排序?

java - 在集合上使用 Java8 流收集到 HashSet 中会出现 `Type Mismatch` 错误

java - IntelliJ IDEA 无法识别 forEach 调用中的方法递归

c# - 打开excel文件时缓冲区不能为空错误?

c# - 如何将此 lambda 表达式转换为 linq