调用局部变量的Java 8方法引用

时间:2015-01-02 19:54:04

标签: java lambda java-8 method-reference

我正在学习Java 8,我遇到了一些我觉得有点奇怪的事情。

请考虑以下代码段:

private MyDaoClass myDao;

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {
    RelationshipTransformer transformer = new RelationshipTransformerImpl();

    myDao.createRelationships(
            relationships.stream()
            .map((input) -> transformer.transformRelationship(input))
            .collect(Collectors.toSet())
    );
}

基本上,我需要将名为relationships的输入集映射到不同的类型 符合DAO I使用的API。对于转换,我想使用我实例化为局部变量的现有RelationshipTransformerImpl类。

现在,我的问题在这里:

如果我要按如下方式修改上述代码:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {
    RelationshipTransformer transformer = new RelationshipTransformerImpl();

    myDao.createRelationships(
            relationships.stream()
            .map((input) -> transformer.transformRelationship(input))
            .collect(Collectors.toSet())
    );

    transformer = null;  //setting the value of an effectively final variable
}

我显然会收到编译错误,因为局部变量transformer不再是&#34;实际上是#34;。但是,如果用方法引用替换lambda:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {
    RelationshipTransformer transformer = new RelationshipTransformerImpl();

    myDao.createRelationships(
            relationships.stream()
            .map(transformer::transformRelationship)
            .collect(Collectors.toSet())
    );

    transformer = null;  //setting the value of an effectively final variable
}

然后我不再收到编译错误!为什么会这样?我认为编写lambda表达式的两种方法应该是等价的,但显然还有更多的东西。

3 个答案:

答案 0 :(得分:22)

JLS 15.13.5可能会有解释:

  

方法参考表达式评估的时间比lambda表达式(第15.27.4节)更复杂。当方法引用表达式在:: separator之前具有表达式(而不是类型)时,将立即计算该子表达式。存储评估结果,直到调用相应功能接口类型的方法为止;此时,结果将用作调用的目标引用。这意味着:: separator之前的表达式仅在程序遇到方法引用表达式时计算,并且不会在函数接口类型的后续调用中重新计算。

据我了解,因为在你的情况下transformer是:: separator之前的表达式,它只被评估一次并存储。由于不必为了调用引用的方法而重新求值,因此transformer稍后被赋值为null无关紧要。

答案 1 :(得分:5)

疯狂的猜测,但对我来说,这是发生的事情......

编译器无法声明创建的流完全同步;它认为这是一种可能的情况:

  • relationships参数创建流;
  • reaffect transformer;
  • stream unrolls。

编译时生成的是一个呼叫站点;它仅在流展开时才链接。

在你的第一个lambda中,你引用一个局部变量,但是这个变量是不是调用站点的一部分。

在第二个lambda中,由于您使用方法引用,这意味着生成的调用站点必须保留对该方法的引用,因此持有该方法的类实例。事实上它是由你后来改变的局部变量引用的并不重要。

我的两分钱......

答案 2 :(得分:5)

在您的第一个示例中,每次调用映射函数时都会引用transformer,因此每次关系都会引用{

}。

在您的第二个示例中transformer仅在transformer::transformRelationship传递给map()时被引用一次。因此,如果事后发生变化并不重要。

那些 不是 &#34;编写lambda表达式&#34; 的两种方法,但是lambda表达式和方法引用,该语言的两个截然不同的特征。