Java 枚举属性根据访问顺序返回 null
Java enum attributes returning null based on order of access
我在 java 中探索枚举以了解它们如何被滥用,我遇到了一种我无法解释的行为。考虑以下 class:
public class PROGRAM {
public enum ENUM {;
public enum ANIMALS {;
public enum CATS {
FELIX(DOGS.AKAME),
GARFIELD(DOGS.WEED),
BUBSY(DOGS.GIN);
CATS(DOGS dog) {this.RIVAL = dog;}
public DOGS RIVAL;
}
public enum DOGS {
GIN(CATS.FELIX), WEED(CATS.BUBSY), AKAME(CATS.GARFIELD);
DOGS(CATS cat) {this.RIVAL = cat;}
public CATS RIVAL;
}
}
}
public static void main(String[] args) {
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
}
}
main 函数中的第一条语句将按预期打印 'WEED'。第二个将打印 'null'。但是,如果您将它们调换,即
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
第一个语句将打印 'FELIX',第二个语句现在将打印 'null'。有没有人可以解释这种现象?
作为参考,我是 运行 Java(TM) SE 运行时环境(build 1.8.0_05-b13)
当您调用 ENUM.ANIMALS.CATS.GARFIELD.RIVAL
时,它将首先创建 CATS 枚举。在处理第一个元素 FELIX 时,它需要创建 DOGS 枚举,以便 DOGS.AKAME 可以作为参数传递给 CATS 构造函数。
DOGS 构造函数接收一个 CATS 类型的参数,但由于 CATS 尚未初始化所有 CATS.something 将 return null
,因此将 RIVAL 属性设置为 null
对于 DOGS 枚举中的所有元素。
创建完所有 DOGS 元素后,它返回到 CATS 并继续创建其元素,将刚刚创建的 DOGS 元素作为参数传递。
类似地,当您颠倒调用顺序时,它首先创建 DOGS 枚举,这会导致 CATS 元素 RIVAL 属性设置为 null
。
如果不清楚,请尝试 运行 在枚举元素的声明和构造函数处设置断点的代码,以便更好地理解它。
这与枚举和 class 初始化有关。
首先,enum
只是一个花哨的 class
,具有常量字段。 That is, the enum constants you declare are in reality just static
fields. 所以
enum SomeEnum {
CONSTANT;
}
编译成类似于
的东西
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
}
其次,static
fields are initialized in the left to right order they appear in the source code.
Next, execute either the class variable initializers and static
initializers of the class, or the field initializers of the interface,
in textual order, as though they were a single block.
在下面
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
public static final SomeEnum CONSTANT_2 = new SomeEnum();
}
CONSTANT
将首先初始化,CONSTANT_2
其次。
第四,如果一个class当前正在被当前线程初始化,你可以正常进行。
If the Class
object for C
indicates that initialization is in progress
for C
by the current thread, then this must be a recursive request for
initialization. Release LC
and complete normally.
这一切是如何组合在一起的?
这个
ENUM.ANIMALS.CATS.GARFIELD.RIVAL
被评价为
CATS cat = ENUM.ANIMALS.CATS.GARFIELD;
DOGS rvial = cat.RIVAL;
第一次访问 GARFIELD
会强制初始化 enum
类型 CATS
。这开始初始化 CATS
中的枚举常量。编译后,那些看起来像
private static final CATS FELIX = new CATS(DOGS.AKAME);
private static final CATS GARFIELD = new CATS(DOGS.WEED);
private static final CATS BUBSY = new CATS(DOGS.GIN);
这些按顺序初始化。所以 FELIX
先行。作为其新实例创建表达式的一部分,它访问 DOGS.AKAME
,其中类型 DOGS
尚未初始化,因此 Java 开始对其进行初始化。 DOGS
枚举类型,编译后看起来像
private static final DOGS GIN = new DOGS(CATS.FELIX);
private static final DOGS WEED = new DOGS(CATS.BUBSY);
private static final DOGS AKAME = new DOGS(CATS.GARFIELD);
所以我们从 GIN
开始。在其新实例创建表达式中,它尝试访问 CATS.FELIX
。 CATS
当前正在初始化,所以我们继续。 CATS.FELIX
尚未分配值。它目前正在堆栈中处于较低的位置。所以它的值为null
。所以 GIN.RIVALS
得到了对 null
的引用。同样的情况发生在所有 DOGS
' RIVAL
.
当DOGS
全部初始化完毕后,执行returns到
private static final CATS FELIX = new CATS(DOGS.AKAME);
其中 DOGS.AKAME
现在指的是完全初始化的 DOGS
对象。它被分配给它的 CATS#RIVAL
字段。每个 CATS
都相同。换句话说,所有 CATS
' RIVAL
字段都分配了一个 DOGS
引用,但反之则不然。
重新排序语句只是确定首先初始化哪个 enum
类型。
我在 java 中探索枚举以了解它们如何被滥用,我遇到了一种我无法解释的行为。考虑以下 class:
public class PROGRAM {
public enum ENUM {;
public enum ANIMALS {;
public enum CATS {
FELIX(DOGS.AKAME),
GARFIELD(DOGS.WEED),
BUBSY(DOGS.GIN);
CATS(DOGS dog) {this.RIVAL = dog;}
public DOGS RIVAL;
}
public enum DOGS {
GIN(CATS.FELIX), WEED(CATS.BUBSY), AKAME(CATS.GARFIELD);
DOGS(CATS cat) {this.RIVAL = cat;}
public CATS RIVAL;
}
}
}
public static void main(String[] args) {
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
}
}
main 函数中的第一条语句将按预期打印 'WEED'。第二个将打印 'null'。但是,如果您将它们调换,即
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
第一个语句将打印 'FELIX',第二个语句现在将打印 'null'。有没有人可以解释这种现象?
作为参考,我是 运行 Java(TM) SE 运行时环境(build 1.8.0_05-b13)
当您调用 ENUM.ANIMALS.CATS.GARFIELD.RIVAL
时,它将首先创建 CATS 枚举。在处理第一个元素 FELIX 时,它需要创建 DOGS 枚举,以便 DOGS.AKAME 可以作为参数传递给 CATS 构造函数。
DOGS 构造函数接收一个 CATS 类型的参数,但由于 CATS 尚未初始化所有 CATS.something 将 return null
,因此将 RIVAL 属性设置为 null
对于 DOGS 枚举中的所有元素。
创建完所有 DOGS 元素后,它返回到 CATS 并继续创建其元素,将刚刚创建的 DOGS 元素作为参数传递。
类似地,当您颠倒调用顺序时,它首先创建 DOGS 枚举,这会导致 CATS 元素 RIVAL 属性设置为 null
。
如果不清楚,请尝试 运行 在枚举元素的声明和构造函数处设置断点的代码,以便更好地理解它。
这与枚举和 class 初始化有关。
首先,enum
只是一个花哨的 class
,具有常量字段。 That is, the enum constants you declare are in reality just static
fields. 所以
enum SomeEnum {
CONSTANT;
}
编译成类似于
的东西final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
}
其次,static
fields are initialized in the left to right order they appear in the source code.
Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.
在下面
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
public static final SomeEnum CONSTANT_2 = new SomeEnum();
}
CONSTANT
将首先初始化,CONSTANT_2
其次。
第四,如果一个class当前正在被当前线程初始化,你可以正常进行。
If the
Class
object forC
indicates that initialization is in progress forC
by the current thread, then this must be a recursive request for initialization. ReleaseLC
and complete normally.
这一切是如何组合在一起的?
这个
ENUM.ANIMALS.CATS.GARFIELD.RIVAL
被评价为
CATS cat = ENUM.ANIMALS.CATS.GARFIELD;
DOGS rvial = cat.RIVAL;
第一次访问 GARFIELD
会强制初始化 enum
类型 CATS
。这开始初始化 CATS
中的枚举常量。编译后,那些看起来像
private static final CATS FELIX = new CATS(DOGS.AKAME);
private static final CATS GARFIELD = new CATS(DOGS.WEED);
private static final CATS BUBSY = new CATS(DOGS.GIN);
这些按顺序初始化。所以 FELIX
先行。作为其新实例创建表达式的一部分,它访问 DOGS.AKAME
,其中类型 DOGS
尚未初始化,因此 Java 开始对其进行初始化。 DOGS
枚举类型,编译后看起来像
private static final DOGS GIN = new DOGS(CATS.FELIX);
private static final DOGS WEED = new DOGS(CATS.BUBSY);
private static final DOGS AKAME = new DOGS(CATS.GARFIELD);
所以我们从 GIN
开始。在其新实例创建表达式中,它尝试访问 CATS.FELIX
。 CATS
当前正在初始化,所以我们继续。 CATS.FELIX
尚未分配值。它目前正在堆栈中处于较低的位置。所以它的值为null
。所以 GIN.RIVALS
得到了对 null
的引用。同样的情况发生在所有 DOGS
' RIVAL
.
当DOGS
全部初始化完毕后,执行returns到
private static final CATS FELIX = new CATS(DOGS.AKAME);
其中 DOGS.AKAME
现在指的是完全初始化的 DOGS
对象。它被分配给它的 CATS#RIVAL
字段。每个 CATS
都相同。换句话说,所有 CATS
' RIVAL
字段都分配了一个 DOGS
引用,但反之则不然。
重新排序语句只是确定首先初始化哪个 enum
类型。