lambda 表达式和实例化方法引用之间的不同行为



据我所知,lambda表达式可以用方法引用代替,没有任何问题。我的 IDE 也说了同样的话,但以下示例显示了相反的情况。 方法引用清楚地返回相同的对象,其中 lambda 表达式每次都会返回新对象。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Instance {
int member;
Instance set(int value){
this.member = value;
return this;
}
@Override
public String toString() {
return member + "";
}
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4);
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4);
List<Instance> collect1 = stream1.map(i -> new Instance().set(i)).collect(Collectors.toList());
List<Instance> collect2 = stream2.map(new Instance()::set).collect(Collectors.toList());
System.out.println(collect1);
System.out.println(collect2);
}
}

这是我的输出:

[1, 2, 3, 4]
[4, 4, 4, 4]

方法参考表达式评估的时间与 哪个 lambda 表达式。
对于在::前面有一个表达式(而不是类型(的方法引用,将立即计算子表达式,然后存储和重用计算结果。
所以在这里:

new Instance()::set

new Instance()被评估一次。

从 15.12.4 开始。方法调用的运行时计算(重点是我的(:

方法参考表达评估的时机比较复杂 比 lambda 表达式 (§15.27.4( 的表达式。当方法引用时 表达式在 :: 之前有一个表达式(而不是类型(: 分隔符,则立即计算该子表达式。结果 评估被存储,直到相应功能的方法 调用接口类型;此时,结果用作 调用的目标引用。这意味着表达式 仅当程序之前计算 :: 分隔符时 遇到方法引用表达式,并且不会在 对函数接口类型的后续调用

您的 lambda 表达式每次执行时都会调用new Instance()。这就解释了为什么每个元素的toString()结果不同。

方法引用保留引用它的实例,因此它类似于:

Instance instance = new Instance();
List<Instance> collect2 = stream2.map(instance::set).collect(Collectors.toList());

在这种情况下使用方法引用的结果是,使用相同的实例来调用set,收集在最后。显示的member值是最后一组。


作为实验,进行这些更改并观察实例在 lambda 表达式的情况下正在更改:

/* a random string assigned per instance */
private String uid = UUID.randomUUID().toString();
Instance set(int value) {
this.member = value;
System.out.println("uid: " + uid); //print the ID
return this;
}

第二个选项的区别在于,在创建流管道时创建一 (1( 个实例。当您在调用终端方法 (toList( 后最终迭代流元素时,在同一实例上调用 set 方法四次,其中最后一个值是最后一个值。生成的列表 (collect2( 包含四次相同的实例。

在第一个项目中,对于流中的每个项目,map()中的 lambda 表达式正在创建一个新的Instance对象。

在第二个中,new Instance()map()开始传递值之前被调用一次。

如果要使用方法引用,请像这样向Instance添加一个构造函数。(我实际上还建议通过使Instance不可变memberfinal,以避免从其他地方出现这样的混淆,就像这样(。

private final int member;
public Instance(int member) {
this.member = member;
}
//remove the setter

然后将流处理更改为如下所示:

List<Instance> collect2 = stream2.map(Instance::new).collect(Collectors.toList());

这样,您可以确保成员在初始化后不会更改,并且简洁地使用方法引用(在这种情况下,构造函数是带有new的方法引用(。

相关内容

  • 没有找到相关文章

最新更新