为什么Java8Lambda允许访问非final类变量?

为什么Java8Lambda允许访问非final类变量?,java,lambda,java-8,Java,Lambda,Java 8,我理解为什么编译器不接受以下内容: class Foo { public Supplier<String> makeSupplier() { String str = "hello"; Supplier<String> supp = () -> return str; // gives the expected compile error because // str is not effec

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

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-Foo{
公共供应商makeSupplier(){
String str=“hello”;
供应商支持=()->返回str;
//给出了预期的编译错误,因为
//str不是有效的最终版本
//(str是一个局部变量,编译时出错
//根据JLS 15.27.2)
str=“世界”;
返回支持;
}
}
让我困惑的是,编译器接受以下内容,而单元测试通过了:

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");
    }
}
类栏{
私有字符串str=“hello”;
公共void setStr(字符串str){
this.str=str;
}
公共供应商makeSupplier(){
供应商支持=()->{return str;};
返回支持;
}
@试验
公共无效单元\u lambdaCapture(){
供应商支持=makeSupplier();
Assert.assertEquals(supp.get(),“hello”);
setStr(“foo”);
Assert.assertEquals(supp.get(),“foo”);
}
}

为什么上述方法有效且工作正常?欢迎指向JLS相关章节的指针(第15.27.2节。仅讨论局部变量)。

它没有。它允许访问有效的最终类变量

一个变量或参数的值在初始化后永远不会改变,它实际上是最终的


来源:

我们都同意,第一个示例不起作用,因为局部变量或参数必须在内部使用

但是您的第二个示例不涉及局部变量或参数,因为
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;
}
另一种观点:您还可以使用普通的旧匿名内部类实现
供应商

public Supplier<String> makeSupplier() {
    return new Supplier<String>() {
        public String get() { return str; }
    };
}
公共供应商makeSupplier(){
退回新供应商(){
公共字符串get(){return str;}
};
}

从内部类访问实例字段是非常常见的,而不是Java 8的特长。

在第二个示例中,您返回一个新的lambda实例,其中包含从上下文捕获的变量,该变量在创建lambda的过程中不会改变
str
是“有效的最终版本”,这就是它被接受的原因。如果您在初始化供应商后尝试重新分配
str
,也会出现编译错误。@fge似乎是回答的候选对象,而不是注释?@PatrickCollins不是重复的,但肯定是与post相关的链接。我相信当lambda引用实例字段时,lambda实际上捕获的是
this
,而不是
this.str
this
保持不变,
this.str
只是在计算lambda时计算的表达式。这可能回答了您的问题:我几乎就要同意了。但是在这种情况下,lambda表达式正在访问一个既不是变量也不是参数的实例字段。实际上,访问的值是一个字段。正如您在引用的文档中所看到的,没有任何东西可以阻止访问非最终字段-