像我们在 C# linq 中一样在 java 8 中获取匿名对象

Posted

技术标签:

【中文标题】像我们在 C# linq 中一样在 java 8 中获取匿名对象【英文标题】:Getting anonymous object in java 8 as we do in C# linq 【发布时间】:2016-12-17 20:47:51 【问题描述】:

假设我有一个 Person 类,其中包含 FirstName、LastName、Age、Salary 等字段。现在我在 C# linq 中有这段代码,其中 people 是一个 List。

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

这将为我提供一个具有 FirstName 和 LastName 的匿名类型。我如何在 Java 8 中编写这样的东西?当我在课堂上说 100 个字段时,只有少数字段返回,还有什么更好的方法。

【问题讨论】:

Java 中没有像 C# 中那样的匿名类型。 (是的,您可以拥有匿名类,但它们在任何有意义的方式上都不尽相同,并且在 Java 8 流中使用起来非常难看。) Java 是严格静态类型的,没有所谓的“匿名类型”。 @dabadaba C# 匿名类型不是动态的——它们完全是静态类型生态系统的一部分。 (它们的语义在编译时强制执行) @dabadaba:正如Kirk 所说,C# 中的匿名类型仍然是静态类型的,而Java 确实 有匿名类——它们只是完全不同。见docs.oracle.com/javase/specs/jls/se8/html/… 还有其他针对 JVM 的语言。例如。 scala 有元组类型。 【参考方案1】:

嗯,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));

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

在 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
);

但是由于这只允许访问单个成员,所以它的用处不大,因为如果您只需要一个成员,您可以首先 map 到成员值。

在这个特定示例中,您可以使用两个元素 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 引用

【讨论】:

如果您立即使用匿名类型进行打印或forEach() 中的任何其他目的,它可以工作。否则,将返回一个 Object,并且由于 Java 没有 var 类型工具来自动推断类型,我们必须将其视为 Stream&lt;Object&gt;。现在我们不能再访问成员了。 @Jagannath:这正是这个答案所说的。

以上是关于像我们在 C# linq 中一样在 java 8 中获取匿名对象的主要内容,如果未能解决你的问题,请参考以下文章

C# linq 的 Java/Android 最佳等效项 [重复]

Java 8 流中的 LINQ Enumerable.Single() [重复]

像JAVA一样流畅调试C#源代码?

利用LINQ to SQL 增删改查本地数据库

Java 等效于 C# Linq 中的 Where 子句

像 c++ 中的 c# 参数一样吗?