在lambda中使用类字段

car*_*ret 3 java lambda java-8

我不明白这种行为.

这段代码符合:

public class A {

    private String s;

    private Function<String, String> f = e -> s;

    public A(String s) {
        this.s = s;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我做s最后的,那么我得到一个编译器错误:

public class A {

    private final String s;

    private Function<String, String> f = e -> s; // Variable 's' might not have been initialized

    public A(String s) {
        this.s = s;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是为什么?如果是另一种方式,我明白了,但是当我声明一个字段final(这迫使我在构造函数中初始化它的值)时,编译器是怎么抱怨的,当它不是时它就可以了final

Ada*_*lik 12

它与lambda无关,这个例子有同样的错误:

public class Test {
    private final String a;
    private String b = a; // // Variable 'a' might not have been initialized

    public Test(String a) {
        this.a = a;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是因为声明位置的初始化是在构造函数之前执行的.因此,在声明的地方b,a仍然没有初始化.

使用此示例时很明显:

public class Test {
    private String a = "init";
    private String b = a;

    public Test(String a) {
        this.a = a;
    }

    public static void main(String[] args) {
        System.out.println(new Test("constructor").b);
    }
}
Run Code Online (Sandbox Code Playgroud)

当你运行它,它打印"init"(到字段的值a最初分配),而不是"constructor"因为初始化b发生之前运行的构造.

你的例子中的lambda使得它更复杂,因为我们可以预期,由于访问a延迟,编译器就可以了,但显然编译器只遵循"不访问变量"的一般规则在它被初始化之前".

您可以使用访问器方法绕过它:

public class Test {
    private final String a;
    private String b = getA(); // allowed now, but not very useful
    private Function<String, String> f = e -> getA(); // allowed now and evaluated at the time of execution of the function

    public Test(String a) {
        this.a = a;
    }

    public static void main(String[] args) {
        System.out.println(new Test("constructor").b); // prints "null"
        System.out.println(new Test("constructor").f.apply("")); // prints "constructor"
    }

    public String getA() {
        return a;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • lambda表达式的基本属性就像周围上下文中的表达式一样工作,因此有关访问空白最终变量的规则是相同的.这与内部类不同.请注意,不是调用`getA()`而是通过使用`Test.this.a`来访问`a`来绕过它. (4认同)
  • 我试过`b = Test.this.a;`几乎每个JDK 8和9都有效.[规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-16.html)明确提到"*变量的简单名称(或者,对于字段,由`this`)限定的字段的简单名称*"当禁止访问空白的最终变量时.你用过Eclipse吗? (2认同)
  • 看起来这是IntelliJ的额外检查.使用原始Oracle javac 1.8.0_131编译`b = Test.this.a`工作正常. (2认同)

Era*_*ran 5

非最终成员变量将始终被初始化(因为它具有默认值 - null在您的String变量的情况下),因此不可能未初始化它.

另一方面,最终变量只能初始化一次,所以我假设它没有初始化为默认值.

我找到的最接近的相关内容是JLS 4.12.4.:

4.12.4.最终变量

变量可以声明为final.最终变量只能分配一次.如果分配了最终变量,那么这是一个编译时错误,除非它在分配之前肯定未分配的

我假设我们可以理解这最后一句话意味着最终变量没有被赋予默认值,否则你将得到一个编译时错误this.s = s;.

更好的JLS参考(感谢Holger)是JLS 16:

第16章.明确的分配

对于本地变量或空白最终字段x的每次访问,必须在访问之前明确分配x,否则发生编译时错误.

这个要求背后的理性是(在你的例子中)lambda表达式可以在s初始化之前调用:

public A(String s) {
    String v = f.apply("x"); // this.s is not initialized at this point
                             // so it can't be accessed
    this.s = s;
}
Run Code Online (Sandbox Code Playgroud)

请注意,您可以在初始化最终变量后初始化构造函数中的lambda表达式(我将参数的名称更改为与成员变量不同,以便lambda表达式不会获取该局部变量):

public A(String so) {
    // f = e -> s; // Error: The blank final field s may not have been initialized
    this.s = so;
    f = e -> s; // works fine
}
Run Code Online (Sandbox Code Playgroud)

  • [JLS§16](https://docs.oracle.com/javase/specs/jls/se8/html/jls-16.html)是正确的地方:"**对于局部变量的每次访问或空白`必须在访问之前明确赋值final`field`x`,`x`,否则会发生编译时错误.**" (3认同)