Атрибуты перечисления 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 и в конструкторах, чтобы лучше понять это.

Другие вопросы по тегам