嗯,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。它不是匿名类,而是用于创建包含某些属性的临时类型的最佳工具。它仍然是一种独特的类型,但可以在本地声明¹并自动获得有用的 equals、hashCode 和 toString 实现。
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 引用