【问题标题】:Java Lambda Consumer Compilation behaviorJava Lambda 消费者编译行为
【发布时间】:2018-03-17 03:10:35
【问题描述】:

我编写了这段代码,但是我不确定为什么会得到如图所示的编译行为。最终对象dataMap 已在构造函数内部分配,因为它通常是这样做的,并且 Lambdas 被懒惰地评估。如果我去掉 final 修饰符,代码编译得很好。

ConsumerTest.java:17: error: variable dataMap might not have been initialized
      dataMap.compute(DataType.EVENS, (k, v) -> {
      ^
ConsumerTest.java:29: error: variable dataMap might not have been initialized
      dataMap.compute(DataType.ODDS, (k, v) -> {
      ^
2 errors

成员变量dataMap 声明为final,并在构造函数中初始化。为什么编译器无法识别这一点并抱怨呢?

import java.util.*;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;

public class ConsumerTest {

  enum DataType {
    EVENS,
    ODDS
  }

  private final Map<DataType, Set<Integer>> dataMap;
  private final IntConsumer allConsumers;

  private IntConsumer evenConsumer = e -> {
    if (e % 2 == 0) {
      dataMap.compute(DataType.EVENS, (k, v) -> {
        if (v == null) {
          v = new HashSet<>();
        }
        v.add(e);
        return v;
      });
    }
  };

  private IntConsumer oddConsumer = e -> {
    if (e % 2 != 0) {
      dataMap.compute(DataType.ODDS, (k, v) -> {
        if (v == null) {
          v = new HashSet<>();
        }
        v.add(e);
        return v;
      });
    }
  };

  public ConsumerTest() {
    dataMap = new HashMap<>();
    allConsumers = evenConsumer.andThen(oddConsumer);
  }

  public static void main(String[] args) {
    ConsumerTest main = new ConsumerTest();
    IntStream.range(10, 31).forEach(main.allConsumers);
    System.out.println(main.dataMap);
  }
}

【问题讨论】:

  • 你只需要初始化它,比如private final Map&lt;DataType, Set&lt;Integer&gt;&gt; dataMap = new Map();
  • @AnthonyKong Map 是一个接口,不能被实例化。你的意思可能是new HashMap&lt;&gt;()
  • 那么HashMap 怎么样?它是您的程序,最终只有您知道要使用哪种数据结构。
  • @AnthonyKong,dataMap 在下面的构造函数中初始化。这里有趣的部分是,通过删除 final 修饰符,这是可行的。
  • 投反对票的人应该注意解释他们这样做的原因

标签: java lambda


【解决方案1】:

问题是您的两个 lambda 表达式可能在初始化之前使用了 datamap 的值。至少,JLS 明确分配规则是这么说的。

这是创建ConsumerTest 对象时会发生的情况:

  1. 分配了裸对象。
  2. 调用超类构造函数 (Object()),它什么也不做。
  3. 实例字段声明被“执行”
    1. dataMap ... 未初始化
    2. allConsumers ... 未初始化
    3. evenConsumer ...初始化为第一个lambda的值
    4. oddConsumer ...初始化为第二个lambda的值
  4. 构造函数的主体被执行
    1. dataMap 已初始化
    2. allConsumers 已初始化。

问题是(概念上)这个。理论上,一旦初始化,这些 lambda 就可以在任何地方传递。如果接收者决定在dataMap 变量初始化之前使用它们,那么 lambda 会看到什么?

  • 如果dataMap 变量不是final,那么他们将看到默认初始值;即null
  • 如果dataMap 是最终结果,则不允许他们看到任何内容。 dataMap 变量必须在使用之前明确分配......因为它是final。 JLS 在这一点上非常特别。

(尝试编写 any 代码在初始化之前读取 final 变量,您会明白我的意思。)

本质上,这只是final 初始化的正常 Java 规则的一个相当意外(尽管完全合乎逻辑)的结果。


在这种情况下我可以想到三个解决方案:

  • 不要将dataMap 变量设为final。不需要。
  • 在其声明中而不是在构造函数中初始化dataMap
  • 在初始化 dataMap 之后,在构造函数中初始化 lambda。 (丑陋......但它会工作。)

【讨论】:

  • 感谢@Stephen C,我在想这里的 lambdas 会被懒惰地评估,在这种情况下,这是在创建 Object 之后很久才发生的。从您的回复Those lambdas could in theory be passed anywhere as soon as they have been initialized 来看,这也可能意味着也可以从构造函数中调用/评估 lambda,在这种情况下,dataMap 的初始化顺序将变得至关重要。由于在编译时无法保证,所以我想它最终会抛出这个错误。
猜你喜欢
  • 2015-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多