Атрибуты перечисления Java, возвращающие нуль в зависимости от порядка доступа
Я изучал перечисления в Java, чтобы увидеть, как они могут быть оскорблены, и я столкнулся с поведением, которое я не мог объяснить. Рассмотрим следующий класс:
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);
}
}
Первое утверждение в основной функции будет выводить "WEED", как и ожидалось. Второй напечатает "ноль". Однако, если вы переключите их, то есть
System.out.println(ENUM.ANIMALS.DOGS.GIN.RIVAL);
System.out.println(ENUM.ANIMALS.CATS.GARFIELD.RIVAL);
первый оператор будет печатать 'FELIX', а второй оператор теперь будет печатать 'null'. Кто-нибудь может объяснить это явление?
Для справки, я использую среду выполнения Java(TM) SE (сборка 1.8.0_05-b13)
2 ответа
Это связано с перечислениями и инициализацией класса.
Первый, enum
это просто фантазия class
с постоянными полями. То есть константы перечисления, которые вы объявляете, в действительности просто static
поля. Так
enum SomeEnum {
CONSTANT;
}
компилируется в нечто подобное
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
}
Во-вторых, static
поля инициализируются в порядке слева направо, они появляются в исходном коде.
Затем выполните инициализаторы переменных класса и статические инициализаторы класса или инициализаторы полей интерфейса в текстовом порядке, как если бы они были единым блоком.
В следующих
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum CONSTANT = new SomeEnum();
public static final SomeEnum CONSTANT_2 = new SomeEnum();
}
CONSTANT
будет инициализирован первым, и CONSTANT_2
второй.
В-третьих, enum
Тип будет [инициализирован][3] при доступе к одной из его констант (которая на самом деле просто static
поле).
В-четвертых, если класс в настоящее время инициализируется текущим потоком, вы продолжаете в обычном режиме.
Если
Class
объект дляC
указывает на то, что инициализация выполняетсяC
текущим потоком, то это должен быть рекурсивный запрос на инициализацию. РелизLC
и завершить нормально.
Как это все получается?
это
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
тип enum, скомпилированный, выглядит как
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
инициализируются, выполнение возвращается к
private static final CATS FELIX = new CATS(DOGS.AKAME);
где DOGS.AKAME
теперь относится к полной инициализации DOGS
объект. Это присваивается CATS#RIVAL
поле. То же самое для каждого из CATS
, Другими словами, все CATS
' RIVAL
поле присваивается DOGS
ссылка, но не наоборот.
Изменение порядка утверждений просто определяет, какие enum
Тип инициализируется первым.
Когда вы звоните ENUM.ANIMALS.CATS.GARFIELD.RIVAL
, это начнется с создания перечисления CATS. При обработке первого элемента, FELIX, ему необходимо создать перечисление DOGS, чтобы DOGS.AKAME можно было передать в качестве параметра конструктору CATS.
Конструктор DOGS получает параметр типа CATS, но, поскольку CATS еще не инициализирован, все CATS будут возвращены. null
таким образом устанавливая атрибут RIVAL в null
для всех элементов в перечислении DOGS.
Когда все элементы DOGS созданы, он возвращается к CATS и возобновляет создание своих элементов, передавая только что созданные элементы DOGS в качестве параметров.
Точно так же, когда вы инвертируете порядок вызовов, он начинается с создания перечисления DOGS, в результате чего атрибут RIVAL элементов CATS устанавливается как null
,
Если это не ясно, попробуйте запустить ваш код с точками останова, установленными в объявлениях элементов enum и в конструкторах, чтобы лучше понять это.