我了解为什么编译器不接受以下内容:
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节仅讨论局部变量)。
我们都同意第一个例子不能用,因为局部变量或参数必须是最终的或有效的最终才能在lambda表达式主体中使用。
但是您的第二个示例不涉及局部变量或参数,而是str实例字段。Lambda表达式可以使用与实例方法相同的方式访问实例字段:
str
15.27.2。 λ体
Lambda主体可以是单个表达式或块(第14.2节)。类似于方法主体,lambda主体描述了每次调用都会执行的代码。
实际上,java编译器lambda$0从您的lambda表达式中创建了一个私有方法,该方法仅访问实例字段str:
lambda$0
private java.lang.String lambda$0() { 0 aload_0; /* this */ 1 getfield 14; /* .str */ 4 areturn; }
另一观点: 您还可以Supplier使用普通的匿名内部类来实现:
Supplier
public Supplier<String> makeSupplier() { return new Supplier<String>() { public String get() { return str; } }; }
从内部类访问实例字段非常普遍,而不是Java 8的专业。