小编典典

为什么Java 8 Lambda允许访问非最终类变量?

java

我了解为什么编译器不接受以下内容:

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节仅讨论局部变量)。


阅读 228

收藏
2020-09-28

共1个答案

小编典典

我们都同意第一个例子不能用,因为局部变量或参数必须是最终的或有效的最终才能在lambda表达式主体中使用

但是您的第二个示例不涉及局部变量或参数,而是str实例字段。Lambda表达式可以使用与实例方法相同的方式访问实例字段:

15.27.2。
λ体

Lambda主体可以是单个表达式或块(第14.2节)。类似于方法主体,lambda主体描述了每次调用都会执行的代码。

实际上,java编译器lambda$0从您的lambda表达式中创建了一个私有方法,该方法仅访问实例字段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的专业。

2020-09-28