无法在正在映射的枚举的初始值设定项中构造 EnumMap
Unable to construct EnumMap inside the initializer of the enum being mapped
在我的代码中,我有一个枚举,其中每个值存储一个单独的 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;
}
为什么会出现此错误,我该如何解决?
简单回答:
首先你应该了解java中的枚举,java中的枚举是一个实例控制的class。这意味着当您定义一个枚举时,您正在定义一个 class 具有固定数量的实例。这些实例是在 class 枚举的 class 由 classloader 加载时在运行时创建的。那么为什么你的代码会抛出 NullPointerException?在这里,当您的 ClassLoader 加载 RPS 时,它会尝试为此枚举创建所有固定实例,并且如您所知,每个实例都有一个称为 matchups 的字段。所以想象一下它试图创建 ROCK 实例,它是 class RPS 的第一个实例,要创建这个实例,它应该调用它的构造函数,你可以在其中初始化匹配。但是这里将如何创建 matchups(这里的 matchups 是一个 EnumMap)? EnumMap 实现使用已经创建和定义的枚举的值(作为第一个通用参数传递给它),但是在这里您是基于它包含的枚举(RPS)来定义 EnumMap,另一方面,RPS 的实例直到他们的 matchups 字段创建了,你可以清楚地看到这里有一个循环。并且您不能以这种递归方式使用枚举。在你的第二个实现中,你在这里做了正确的事情,你延迟了对战的创建,并在 RPS 枚举创建并准备好后初始化了这个文件,这样对战的构建就会成功。
更专业的回答:
EnumMap 实现在内部使用数组来实现这个结构。并在构建时创建一个元素大小的数组,这些元素存在于传递给它的枚举中。在 EnumMap 实现中有一个名为 getKeyUniverse(Class keyType) 的方法,其中 Returns 所有包含 K 的值,请注意这里的 K 是您作为第一个参数传递给 EnumMap 的枚举(这里 K 是RPS), getKeyUniverse(Class keyType) 方法在构造函数中调用,当你想用new操作符创建新的实例时,因为此时RPS还没有完全创建这个方法不能return所有的值包含 RPS,因此 return 为空。并在此数组(为 null)上调用 lentch 抛出 NullPointerException。方法文档中明确定义了抛出此异常。
只需将初始化移动到静态块,因为 RPS 枚举值将在那时确定:
enum RPS {
ROCK,
PAPER,
SCISSORS;
EnumMap<RPS,Boolean> matchups;
static
{
for (RPS val:RPS.values())
val.matchups = new EnumMap<>(RPS.class);
}
}
在我的代码中,我有一个枚举,其中每个值存储一个单独的 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;
}
为什么会出现此错误,我该如何解决?
简单回答:
首先你应该了解java中的枚举,java中的枚举是一个实例控制的class。这意味着当您定义一个枚举时,您正在定义一个 class 具有固定数量的实例。这些实例是在 class 枚举的 class 由 classloader 加载时在运行时创建的。那么为什么你的代码会抛出 NullPointerException?在这里,当您的 ClassLoader 加载 RPS 时,它会尝试为此枚举创建所有固定实例,并且如您所知,每个实例都有一个称为 matchups 的字段。所以想象一下它试图创建 ROCK 实例,它是 class RPS 的第一个实例,要创建这个实例,它应该调用它的构造函数,你可以在其中初始化匹配。但是这里将如何创建 matchups(这里的 matchups 是一个 EnumMap)? EnumMap 实现使用已经创建和定义的枚举的值(作为第一个通用参数传递给它),但是在这里您是基于它包含的枚举(RPS)来定义 EnumMap,另一方面,RPS 的实例直到他们的 matchups 字段创建了,你可以清楚地看到这里有一个循环。并且您不能以这种递归方式使用枚举。在你的第二个实现中,你在这里做了正确的事情,你延迟了对战的创建,并在 RPS 枚举创建并准备好后初始化了这个文件,这样对战的构建就会成功。
更专业的回答:
EnumMap 实现在内部使用数组来实现这个结构。并在构建时创建一个元素大小的数组,这些元素存在于传递给它的枚举中。在 EnumMap 实现中有一个名为 getKeyUniverse(Class keyType) 的方法,其中 Returns 所有包含 K 的值,请注意这里的 K 是您作为第一个参数传递给 EnumMap 的枚举(这里 K 是RPS), getKeyUniverse(Class keyType) 方法在构造函数中调用,当你想用new操作符创建新的实例时,因为此时RPS还没有完全创建这个方法不能return所有的值包含 RPS,因此 return 为空。并在此数组(为 null)上调用 lentch 抛出 NullPointerException。方法文档中明确定义了抛出此异常。
只需将初始化移动到静态块,因为 RPS 枚举值将在那时确定:
enum RPS {
ROCK,
PAPER,
SCISSORS;
EnumMap<RPS,Boolean> matchups;
static
{
for (RPS val:RPS.values())
val.matchups = new EnumMap<>(RPS.class);
}
}