有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

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


共 (2) 个答案

  1. # 1 楼答案

    我们都同意第一个例子不能作为局部变量使用,或者参数必须是final or effectively final才能在lambda expression body中使用

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

    15.27.2. Lambda Body

    A lambda body is either a single expression or a block (§14.2). Like a method body, a lambda body describes code that will be executed whenever an invocation occurs.

    事实上,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; }
        };
    }
    

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