0

在我的代码中,我有一个枚举,其中每个值都存储一个单独的 EnumMap。但是,当我尝试在构造函数或初始化程序中初始化 EnumMap 时,使用以下代码:

public static void main(String[] args) {
   RPS.values(); // forces initialization of enum values
}

enum RPS {
   ROCK,
   PAPER,
   SCISSORS;
   EnumMap<RPS,Boolean> matchups;
   {
      matchups = new EnumMap<>(RPS.class);
   }
}

它抛出由 NullPointerException 引起的 ExceptionInInitializerError。但是,当我在构造函数之外对其进行初始化时,不会引发错误,如下面的代码所示:

public static void main(String[] args) {
   for (RPS val:RPS.values())
      val.matchups = new EnumMap<>(RPS.class);
}

enum RPS {
   ROCK,
   PAPER,
   SCISSORS;
   EnumMap<RPS,Boolean> matchups;
}

为什么会发生此错误,我该如何解决?

4

2 回答 2

0

只需将您的初始化移动到静态块,因为 RPS 枚举值由那时确定:

enum RPS {
   ROCK,
   PAPER,
   SCISSORS;

   EnumMap<RPS,Boolean> matchups;
   static
   {
        for (RPS val:RPS.values())
            val.matchups = new EnumMap<>(RPS.class);
   }
}
于 2020-06-09T10:09:20.927 回答
0

简单的回答:

首先你应该对java中的enum有所了解,java中的enum是一个实例控制的类。这意味着当您定义一个枚举时,您正在定义一个具有固定数量实例的类。这些实例是在运行时由类加载器加载枚举类时创建的。那么为什么你的代码会抛出 NullPointerException 呢?在这里,当您的 ClassLoader 加载 RPS 时,它会尝试为此枚举创建所有固定实例,并且您知道每个实例都有一个称为 matchups 的字段。所以想象一下它试图创建 ROCK 实例,这是 RPS 类的第一个实例,要创建这个实例,它应该调用它的构造函数来初始化比赛。但是如何在这里创建对局(这里的对局是一个 EnumMap)?EnumMap 实现使用已创建和定义的枚举的值(作为第一个通用参数传递给它),但是在这里,您是根据包含 RPS 的枚举来定义 EnumMap,另一方面,在创建匹配字段之前无法创建 RPS 实例,您可以清楚地看到这里有一个循环。而且您不能以这种递归方式使用枚举。在您的第二个实现中,您在这里做了正确的事情,您延迟了对战的创建,并在 RPS 枚举创建并准备好之后初始化了这个文件,因此这种对战的构建将是成功的。

更专业的答案:

EnumMap 实现内部使用数组来实现这个结构。并在构造时创建一个大小元素的数组,这些元素存在于传递给它的枚举中。在 EnumMap 实现中,有一个名为 getKeyUniverse(Class keyType) 的方法,它返回包含 K 的所有值,注意这里的 K 是您作为第一个参数传递给 EnumMap 的枚举(这里 K 是 RPS),getKeyUniverse(Class keyType)当您想使用 new 运算符创建新实例时,在构造函数中调用该方法,因为此时 RPS 尚未完全创建,该方法无法返回包含 RPS 的所有值,因此返回 null。并在此数组(为空)上调用 lentch 会抛出 NullPointerException。方法文档中明确定义了抛出此异常。

于 2020-06-08T19:03:59.400 回答