lambda 表达式和实例化方法引用之间的不同行为
Different behavior between lambda expression and method reference by instantiation
据我所知,lambda 表达式可以被方法引用替换,没有任何问题。我的 IDE 说的是一样的,但下面的例子显示了相反的情况。
该方法清楚地引用 returns 同一个对象,其中 lambda 表达式每次 returns 新对象。
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()
。这解释了为什么其 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;
}
第二个选项的不同之处在于,您在创建 Stream 管道时创建了一个 (1) 实例。当您在调用终端方法 (toList) 后最终迭代流元素时,您会在同一实例上调用 set 方法四次,其中最后一个值是最后一个值。结果列表 (collect2) 包含 相同 实例的四倍。
在第一个中,对于流中的每个项目,map()
中的 lambda 表达式正在创建一个新的 Instance
对象。
在第二个中,new Instance()
在 map()
开始传递值之前被调用一次。
如果要使用方法引用,请像这样向 Instance
添加构造函数。 (我实际上还建议通过使 member
final
使 Instance
不可变,这样您就可以避免在其他地方像这样混淆)。
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
的方法引用)。
方法引用表达式求值的时间不同于
哪个 lambda 表达式。
对于在 ::
之前具有表达式(而不是类型)的方法引用,子表达式会立即求值,然后存储并重用求值结果。
所以在这里:
new Instance()::set
new Instance()
计算一次。
来自 15.12.4. Run-Time Evaluation of Method Invocation(强调是我的):
The timing of method reference expression evaluation is more complex
than that of lambda expressions (§15.27.4). When a method reference
expression has an expression (rather than a type) preceding the ::
separator, that subexpression is evaluated immediately. The result of
evaluation is stored until the method of the corresponding functional
interface type is invoked; at that point, the result is used as the
target reference for the invocation. This means the expression
preceding the :: separator is evaluated only when the program
encounters the method reference expression, and is not re-evaluated on
subsequent invocations on the functional interface type.
据我所知,lambda 表达式可以被方法引用替换,没有任何问题。我的 IDE 说的是一样的,但下面的例子显示了相反的情况。 该方法清楚地引用 returns 同一个对象,其中 lambda 表达式每次 returns 新对象。
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()
。这解释了为什么其 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;
}
第二个选项的不同之处在于,您在创建 Stream 管道时创建了一个 (1) 实例。当您在调用终端方法 (toList) 后最终迭代流元素时,您会在同一实例上调用 set 方法四次,其中最后一个值是最后一个值。结果列表 (collect2) 包含 相同 实例的四倍。
在第一个中,对于流中的每个项目,map()
中的 lambda 表达式正在创建一个新的 Instance
对象。
在第二个中,new Instance()
在 map()
开始传递值之前被调用一次。
如果要使用方法引用,请像这样向 Instance
添加构造函数。 (我实际上还建议通过使 member
final
使 Instance
不可变,这样您就可以避免在其他地方像这样混淆)。
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
的方法引用)。
方法引用表达式求值的时间不同于
哪个 lambda 表达式。
对于在 ::
之前具有表达式(而不是类型)的方法引用,子表达式会立即求值,然后存储并重用求值结果。
所以在这里:
new Instance()::set
new Instance()
计算一次。
来自 15.12.4. Run-Time Evaluation of Method Invocation(强调是我的):
The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.