繁体   English   中英

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

[英]Java lambdas have different variable requirements than anonymous inner classes

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

关于lambda 表达式主体的章节指出

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

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

因此,他们更加严格。

在这种情况下,周围的上下文是对字段的赋值,而手头的问题是对表达式右侧的字段val的访问,这是一个空白的final字段。

Java 语言规范指出

每个局部变量(第 14.4 节)和每个空白的final字段(第 4.12.4 节,第 8.3.1.2 节)在对其值进行任何访问时都必须具有明确分配的值。

对其值的访问包括变量的简单名称(或者,对于字段,由this限定的字段的简单名称)出现在表达式中的任何地方,除了简单赋值运算符= (§ 15.26.1)。

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

然后它继续说

C为一个类,并设VC的空白finalstatic成员字段,在C声明。 然后:

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

  • V是[未]初始化一个实例或实例变量初始值的前分配C比最左边当且仅当其它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 = ...

因此,当在lambdaGetValString的初始化表达式中访问该val时,编译器会确定该val处于未分配状态。

上述规则适用于简单名称val ,而不适用于限定表达式this.val 你可以使用

final Callable<String> lambdaGetValString = () -> String.valueOf(this.val);

这不会编译:

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”)尚未初始化。

就我而言,我有一个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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM