我理解为什么编译器不接受以下内容:
class Foo {
public Supplier<String> makeSupplier() {
String str = "hello";
Supplier<String> supp = () -> return str;
// gives the expected compile error because
// str is not effectively final
// (str is a local variable, compile-time error
// as per JLS 15.27.2.)
str = "world";
return supp;
}
}
让我感到困惑的是编译器接受以下内容,并且单元测试通过了:
class Bar {
private String str = "hello";
public void setStr(String str) {
this.str = str;
}
public Supplier<String> makeSupplier() {
Supplier<String> supp = () -> { return str; };
return supp;
}
@Test
public void Unit_lambdaCapture() {
Supplier<String> supp = makeSupplier();
Assert.assertEquals(supp.get(), "hello");
setStr("foo");
Assert.assertEquals(supp.get(), "foo");
}
}
为什么上述有效且正常工作?欢迎使用JLS相关部分的指针(第15.27.2节,仅讨论局部变量)。
答案 0 :(得分:6)
我们都同意第一个示例不起作用,因为局部变量或参数必须final or effectively final才能在lambda expression body中使用。
但是你的第二个例子不涉及局部变量或参数,因为str
是一个实例字段。 Lambda表达式可以像实例方法一样访问实例字段:
lambda主体是单个表达式或块(第14.2节)。像方法体一样,lambda体描述了每当调用发生时都会执行的代码。
实际上,java编译器会从lambda表达式中创建一个私有方法lambda$0
,只需访问实例字段str
:
private java.lang.String lambda$0() {
0 aload_0; /* this */
1 getfield 14; /* .str */
4 areturn;
}
另一种观点:您还可以使用普通的匿名内部类来实现Supplier
:
public Supplier<String> makeSupplier() {
return new Supplier<String>() {
public String get() { return str; }
};
}
从内部类访问实例字段非常常见,而不是Java 8的专业。
答案 1 :(得分:0)
没有。它允许有效地访问最终的类变量。
一个变量或参数,其值在初始化后永远不会改变,实际上是最终的。
来源:http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html