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

时间:2016-06-24 18:41:37

标签: java lambda anonymous-inner-class

我有一个匿名的内部类和一个等效的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);
}

编辑:我确实遇到过一种解决方法:使用val的getter。

3 个答案:

答案 0 :(得分:11)

关于lambda expression bodies州的章节

  

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

     

this(显性和隐性)的透明度   lambda表达式 - 也就是说,将其视为与...相同   周围环境 - 允许更多的实施灵活性,以及   防止身体中不合格名字的含义   取决于超载分辨率。

因此,他们更加严格。

在这种情况下,周围的上下文是对字段的分配,而当前的问题是对字段val,空白final字段的访问,位于右侧表达。

Java语言规范说明

  

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

     

对其值的访问权限包含变量的简单名称   (或者,对于字段,由this限定的字段的简单名称)   发生在表达式中的任何地方,除了作为左边的操作数   简单赋值运算符=(第15.26.1节)。

     

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

然后继续说

  

C成为一个类,让V成为空白finalstatic成员字段   在C中声明的C。然后:

     
      在最左边的实例初始化程序(第8.6节)或实例变量之前,
  • V肯定是未分配的(而且未明确分配)   初始化程序为C

  •   
  • V在[{1}}的实例初始值设定项或实例变量初始值设定项之前[un]分配,而最左边的iff C除外   在前面的实例初始化程序或实例之后分配[un]   变量初始值设定项V

  •   

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

C

因此,编译器在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 = ... 的初始化表达式中访问时,确定val未分配。

上述规则适用于使用简单名称lambdaGetValString,而不适用于合格的表达式val。你可以使用

this.val

答案 1 :(得分:3)

这不会编译:

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无关。 在执行构造函数之前,将对在其声明的同一行上初始化的字段进行求值。 所以在这一点上,变量&#39; val&#39; (或在此示例中&#39; x&#39;)尚未初始化。

答案 2 :(得分:0)

就我而言,我有一个Predicate试图访问一个private final实例变量。我也完成了Predicate决赛的修复。

之前-编译器错误,可能尚未初始化this.availableCities

class Service {
  private final List<String> availableCities;
  Service(List<String> availableCities) {
    this.availableCities = availableCities;
  }
  private Predicate<String> isCityAvailable = city -> this.availableCities.contains(city);
}

之后-没有更多错误

class Service {
  private final List<String> availableCities;
  private final Predicate<String> isCityAvailable;
  Service(List<String> availableCities) {
    this.availableCities = availableCities;
    this.isCityAvailable = city -> this.availableCities.contains(city);
  }
}

我认为“可能尚未初始化”编译器错误有一些优点,因为否则,没有什么可以阻止您在初始化最终实例变量之前从构造函数中调用Predicate

编译器强制执行此操作的可能原因

class Service {
  private final List<String> availableCities;
  Service(List<String> availableCities, String topCity) {
    boolean isTopCityAvailable = isCityAvailable.test(topCity); // Error: this.availableCities is not initialized yet
    this.availableCities = availableCities;
  }
  private Predicate<String> isCityAvailable = city -> this.availableCities.contains(city);
}