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

时间:2018-08-30 12:35:59

标签: java lambda java-stream method-reference

据我所知,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]

4 个答案:

答案 0 :(得分:6)

您的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 :(得分:6)

方法参考表达式评估的时间与 哪种lambda表达式。
使用在::之前具有表达式(而不是类型)的方法引用,将立即评估子表达式,然后存储评估结果并重新使用。
所以在这里:

new Instance()::set

new Instance()被评估一次。

来自15.12.4. Run-Time Evaluation of Method Invocation(重点是我的):

  

方法参考表达式评估的时间更加复杂   比lambda表达式(第15.27.4节)大。方法参考时   expression在:: ::之前有一个表达式(而不是类型)   分隔符,该子表达式将立即求值。的结果   评估被存储,直到相应功能的方法   接口类型被调用;此时,结果将用作   调用的目标参考。 这表示   ::分隔符前面的内容仅在程序   遇到方法引用表达式,并且不重新评估   功能接口类型上的后续调用

答案 2 :(得分:2)

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

答案 3 :(得分:2)

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

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

如果要使用方法引用,请像这样向Instance添加一个构造函数。 (实际上,我还建议通过使Instance member来使final不可变,以免像这样在其他地方引起混乱。)

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的方法引用)。