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

Java 8 Method references called on a local variable

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

考虑以下片段:

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 的 API。对于转换,我想使用我实例化为局部变量的现有 RelationshipTransformerImpl class。

现在,这是我的问题:

如果我将上面的代码修改如下:

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 不再是 "effectively final"。但是,如果用方法引用替换 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 表达式的方法应该是等价的,但显然还有更多的事情要做。

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

编译器根本无法断言创建的流是同步的;它认为这是一种可能的情况:

  • relationships 参数创建流;
  • 重新影响 transformer
  • 流展开。

编译时生成的是调用点;它仅在流展开时链接。

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

在第二个 lambda 中,由于您使用了方法引用,这意味着生成的调用站点必须保留对该方法的引用,因此 class 实例持有该方法。它是由您之后更改的局部变量引用的事实无关紧要。

我的两分钱...

JLS 15.13.5可装解释:

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.

据我了解,由于在您的情况下 transformer 是 :: 分隔符之前的表达式,因此它仅被计算一次并存储。由于不必重新评估它来调用引用的方法,因此 transformer 稍后分配为 null 并不重要。

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

在您的第二个示例中,当 transformer::transformRelationship 传递给 map() 时,transformer 仅被引用一次。所以以后改了也没关系

它们 不是 "the two ways to write the lambda expression" 而是一个 lambda 表达式和一个方法引用,这是该语言的两个截然不同的特征。