Java lambdas具有与匿名内部类不同的变量要求

2022-09-02 01:55:55

我有一个匿名的内部类和一个等效的lambda。为什么 lambda 的变量初始化规则更严格,是否有比匿名内部类或在构造函数中初始化它更干净的解决方案?

import java.util.concurrent.Callable;

public class Immutable {
    private final int val;

    public Immutable(int val) { this.val = val; }

    // Works fine
    private final Callable<String> anonInnerGetValString = new Callable<String>() {    
        @Override
        public String call() throws Exception {
            return String.valueOf(val);
        }
    };

    // Doesn't compile; "Variable 'val' might not have been initialized"
    private final Callable<String> lambdaGetValString = () -> String.valueOf(val);
}

编辑:我确实遇到了一个解决方法:使用getter for .val


答案 1

关于 lambda 表达式正文的章节声明

与匿名类声明中出现的代码不同,名称的含义和 lambda 主体中出现的 thissuper 关键字以及引用声明的可访问性与周围上下文中的含义相同(除了 lambda 参数引入了新名称)。

lambda 表达式主体中(显式和隐式)的透明度( 即,将其视为与周围上下文中的相同 - 为实现提供了更大的灵活性,并防止主体中非限定名称的含义依赖于重载解析。this

正因为如此,他们更加严格。

在本例中,周围的上下文是对字段的赋值,手头的问题是表达式右侧的字段 (空白字段) 的访问。valfinal

Java 语言规范声明

每个局部变量 (§14.4) 和每个空白字段 (§4.12.4, §8.3.1.2) 在访问其值时必须具有明确赋值的值。final

对其值的访问由变量的简单名称或者,对于字段,由限定的字段的简单名称)组成,该变量出现在表达式中的任何位置,但作为简单赋值运算符 (§15.26.1) 的左侧操作数除外。=

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

然后它继续说

设为类,并设为 的空白非成员字段,在 中声明。然后:CVfinalstaticCC

  • V在 的最左侧的实例初始值设定项 (§8.6) 或 的实例变量初始值设定项之前绝对未赋值(并且未明确赋值)。C

  • VC 的实例初始值设定项或实例变量初始值设定项之前 [un]赋值,最左边的 V 在 C 的前一个实例初始值设定项或实例变量初始值设定项之后 [un]赋值。

你的代码基本上看起来像这样

private final int val;
// leftmost instance variable initializer, val still unassigned 
private final Callable<String> anonInnerGetValString = ...
// still unassigned after preceding variable initializer
private final Callable<String> lambdaGetValString = ...

因此,编译器在 的初始化表达式中访问时确定为 未赋值。vallambdaGetValString


答案 2

这不会编译:

public class Example
{
  private final int x;
  private final int y = 2 * x;

  public Example() {
    x = 10;
  }
}

但这会:

public class Example
{
  private final int x;
  private final int y;

  public Example() {
    x = 10;
    y = 2 * x;
  }
}

这个也是如此:

public class Example
{
  private final int x = 10;
  private final int y = 2 * x;
}

所以这与lambdas无关。在执行构造函数之前,将计算在声明该字段的同一行上初始化的字段。因此,此时变量“val”(或本例中的“x”)尚未初始化。


推荐