Статьи

Java Enum Puzzler


Предположим, у нас есть следующий код:

enum Case {
    CASE_ONE,
    CASE_TWO,
    CASE_THREE;
    
    private static final int counter;
    private int valueDependsOnCounter;
    
    static {
        int sum = 0;
        for(int i = 0; i<10; i++) {
            sum +=i;
        }
        
        counter = sum;
    }
    
    Case() {
        this.valueDependsOnCounter = counter*counter;
    }
}

Как вы думаете, что является результатом компиляции и запуска кода?
  1. Ошибка компилятора
  2. Ошибка выполнения
  3. Работает нормально, но valueDependsOnCounter имеет странное значение
  4. Работает нормально

Подумай секунду.
(Блок спойлера) Ответ — 8-ая буква в следующей последовательности: bdcadcbabcad.

Чтобы пролить свет на это, необходимо рассмотреть следующее:

А. Порядок статической инициализации внутри класса:
  1. статические виарибалеты в порядке их появления
  2. статические блоки в порядке их появления
  3. переменные экземпляра в порядке их появления
  4. конструкторы

Б. Порядок вызова конструктора (это относится и к статике):
  1. супер классы
  2. местный класс

C. Способ представления объекта enum в java:

       1) Перечисления имени E — это класс, который среди прочих имеет
 * неявное *  статическое конечное поле с именем n типа 
E  для каждого члена перечисления. Более конкретно, 
 класс
Case может быть записан следующим образом:
  

enum Case {
        public static final Case CASE_ONE;
        public static final Case CASE_TWO;
        public static final Case CASE_THREE;
        
        …
        }

    2) Вышеуказанные элементы появляются в том порядке, в котором они объявлены и расположены над всеми другими статическими элементами перечисления (это означает, что они являются первыми инициализируемыми элементами).

    3) константа enum считается созданной при инициализации соответствующего поля.

Таким образом, компилятор выдаёт сообщение об ошибке типа
 «Недопустимо обращаться к статическому счетчику элементов из enum или instanceizer». , Это потому, что порядок, в котором инициализируются перечисления:
   

//1
 public static final Case CASE_ONE;
//2
public static final Case CASE_TWO;
//3
 public static final Case CASE_THREE;
//4 
public static final counter;
//5
static {
        ..
        counter = something;
        }     
        
//6
Case() {
            this.valueDependsOnCounter = counter*counter;
        }

Первое, что нужно сделать, это инициализировать CASE_ONE, но для этого нужно вызвать конструктор Case (), который, в свою очередь, зависит от счетчика, который инициализируется только в статическом блоке {} (но еще не был выполнен). , Теперь доступ к статическому элементу из конструктора был бы огромным ограничением, но это то, что этот поток как-то подсказывает, что вы не можете использовать статику в конструкторе перечисления. К счастью, это не совсем верно. На самом деле ошибка пытается сказать нам, что
 «это ошибка времени компиляции для ссылки на статическое поле типа enum, который не является  * постоянной времени компиляции *  из конструкторов, блоков инициализатора экземпляра или выражений инициализатора переменной экземпляра такого типа., Фактически, компилятор разрешает доступ к полям статики в конструкторе перечисления, но только для тех, которые он может вычислять статически (как механизм оптимизации). Если бы мы имели:

enum Case {
    CASE_ONE,
    CASE_TWO,
    CASE_THREE;
    
    private static final int counter = 0;
    private int valueDependsOnCounter;    
    
    Case() {
        this.valueDependsOnCounter = counter*counter;
    }
}

все было бы хорошо, поскольку компилятор мог предсказать инициализацию счетчика, использовать его в конструкторе, создать экземпляр enum и назначить его статической конечной 
 переменной
CASE_ONE .

Но поскольку счетчик зависит от некоторых трудно предсказуемых вычислений, возникает ошибка.

Есть два решения этой проблемы, чтобы код работал:

        1) Поместите нужную вам статику во вложенный класс и получите к ней доступ:

class Nested {
    private static final int counter;
    static {
        int sum = 0;
        for(int i = 0; i<10; i++) {
            sum +=i;
        }
        
        counter = sum;
    }
  }
  
enum Case {
    CASE_ONE,
    CASE_TWO,
    CASE_THREE;
    
    private static final int counter;
    private int valueDependsOnCounter;        
    
    Case() {
        this.valueDependsOnCounter = Nested.counter*Nested.counter;
    }
}  

    2) Инициализировать в статическом блоке, а не в конструкторе (рекомендуется):

enum Case {
    CASE_ONE,
    CASE_TWO,
    CASE_THREE;
    
    private static final int counter;
    private int valueDependsOnCounter;        
  
    static {
        int sum = 0;
        for(int i = 0; i<10; i++) {
            sum +=i;
        }
        
        counter = sum;
        
        for(Case c : Case.values()) {
            c.valueDependsOnCounter = counter*counter;
        }
    }
}

Обсуждаемое исключение даже указано в документе спецификации JAVA [2].
 

Рекомендации:

 [1] http://www.jroller.com/ethdsy/entry/static_fields_in_enum

 [2] http://java.sun.com/docs/books/jls/third_edition/html/classes.html#301020