27

我在学习 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 的 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不再是“有效的最终”。但是,如果将 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 表达式的两种方法应该是等价的,但显然还有更多事情要做。

4

3 回答 3

24

JLS 15.13.5可能有以下解释:

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

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

于 2015-01-02T20:26:39.440 回答
5

In your first example, transformer is referenced every time the mapping function is called, so once for every relationship.

In your second example transformer is referenced only once, when transformer::transformRelationship is passed to map(). So it doesn't matter if it changes afterward.

Those are not "the two ways to write the lambda expression" but a lambda expression and a method reference, two distinct features of the language.

于 2015-01-02T20:33:26.610 回答
5

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

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

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

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

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

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

我的两分钱...

于 2015-01-02T20:20:41.150 回答