Почему это перечисление компилируется?

Я хотел создать enum, где каждая константа имеет связанный с ней Map. Я выполнил это, указав каждой константе инициализатор экземпляра, например:

import java.util.HashMap;
import java.util.Map;

public enum Derp {
    FOO {{
            mMap.put("bar", 1);
        }};

    // cannot be private
    protected final Map<String, Integer> mMap = new HashMap<>();
}

Я обнаружил, что если mMap есть private, на него нельзя ссылаться в инициализаторе экземпляра. Ошибка Cannot make a static reference to the non-static field mMap. Прежде чем причина для этого возникла, я посоветовался с JLS §8.9.2, в котором говорится, в частности:

Это ошибка времени компиляции для конструкторов, блоков инициализатора экземпляра или выражений инициализатора экземпляра константы перечисления e для ссылки на e или на константу перечисления того же типа, которая объявлена ​​в справа от e.

Разве я не нарушаю это правило, неявно ссылаясь на FOO в FOO собственный экземпляр экземпляра? Как это компилируется? Он не только компилируется, но работает корректно во время выполнения.

(Мне пришло в голову, что mMap не может быть private, потому что я неявно создаю анонимный подкласс, который не может ссылаться на поле private в своем суперклассе. Это немного странно, поскольку перечисления неявно final...)

Ответы

Ответ 1

Это ошибка времени компиляции для конструкторов, блоков инициализатора экземпляра или выражений инициализатора экземпляра константы перечисления e для ссылки на e или на константу перечисления того же типа, которая объявлена ​​в справа от e.

Спецификация здесь означает, что вы не можете ссылаться по имени, потому что поле, на которое указывает e, еще не инициализировано. Это не значит, что вы не можете получить доступ к this.

Это в основном то же самое правило, что и любой другой инициализатор (например, int x = x;).

Мы можем понять, почему с примером вроде (Ideone):

enum Example {
    INSTANCE {{
        subversion();
    }};

    static void subversion() {
        System.out.println(INSTANCE);
    }

    public static void main(String[] args) {
        System.out.println(INSTANCE);
    }
}

Какие выходы

null
INSTANCE

Я обнаружил, что если mMap является закрытым, на него нельзя ссылаться в инициализаторе экземпляра.

Вы можете квалифицировать вызов как super.mMap.put(...);. Частный mMap не унаследован, но доступен из внутреннего класса. Я также рассказал об этом здесь. Короче говоря, простое имя mMap относится к несуществующему внешнему экземпляру Derp.

Мы можем проверить, что это имеет место с примером вроде (Ideone):

class Example {
    private int x;

    class Inner extends Example {{
        x = 1;       // refers to the outer instance
        super.x = 2; // refers to the inner instance
    }}

    public static void main(String[] args) {
        Example outer = new Example();
        Example inner = outer.new Inner();
        System.out.println(outer.x); // prints 1
        System.out.println(inner.x); // prints 2
    }
}

За исключением вашего случая FOO является статическим, поэтому внешний экземпляр &mdash отсутствует, поэтому ошибка компилятора.

Ответ 2

Это потому, что FOO является собственным анонимным подклассом Derp - который уже существует при создании FOO.

public class Enums {
    public enum Derp {
        FOO {{
            mMap.put("bar", 1);
        }};

        // cannot be private
        protected final Map<String, Integer> mMap = new HashMap<>();
    }

    public static void main(String[] args) {
        System.out.println(Derp.class);
        System.out.println(Derp.FOO.getClass());
    }
}

класс Enums $Derp
класс Enums $Derp $1