我正在学习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
不再是"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表达式的两种方法应该是等价的,但显然还有更多的事情要发生 .
3 回答
JLS 15.13.5可能会有解释:
据我了解,因为在你的情况下
transformer
是:: separator之前的表达式,它只被评估一次并存储 . 因为transformer
稍后被指定为null并不重要 .疯狂的猜测,但对我来说,这是发生了什么......
编译器无法声明创建的流完全是同步的;它认为这是一种可能的情况:
从
relationships
参数创建流;reaffect
transformer
;stream展开 .
编译时生成的是一个调用站点;它仅在流展开时链接 .
在您的第一个lambda中,您引用一个局部变量,但此变量不是调用站点的一部分 .
在第二个lambda中,由于您使用方法引用,这意味着生成的调用站点必须保留对该方法的引用,因此持有该方法的类实例 . 事实上它是由你后来改变的局部变量引用的并不重要 .
我的两分钱......
在您的第一个示例中,每次调用映射函数时都会引用
transformer
,因此每次关系都会引用一次 .在第二个示例中,当
transformer::transformRelationship
传递给map()
时,transformer
仅被引用一次 . 所以如果之后发生变化并不重要 .这些是 not "the two ways to write the lambda expression",但是lambda表达式和方法引用,是语言的两个不同特征 .