Почему я не могу получить доступ к статическим конечным членам из выделенного значения enum в Java

Мне было интересно, почему, хотя совершенно справедливо сделать следующее в Java

public enum Test {
   VALUE1() {
      public static final String CONST_RELATED_TO_VALUE1 = "constant";
      public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
   },
   VALUE2() {
      public static final String CONST_RELATED_TO_VALUE2 = "constant";
   },
   VALUE3;
}

доступ к константам, как можно было бы ожидать, используя Test.VALUE1.CONST_RELATED_TO_VALUE1 не работает.

Теперь я понимаю, что VALUE1, VALUE2 и т.д. на самом деле все обычно рассматриваются как статический окончательный экземпляр типа Test и, следовательно, не имеют этих полей, но информация должна теоретически доступна во время компиляции, что можно легко проверить, выполнив небольшой тест

     // print types and static members
     for (Object o: Test.values()) {
        System.out.println(o.toString() + ": " + o.getClass());
        for (Field field : o.getClass().getDeclaredFields()) {
            if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                System.out.println("\t" + field);
            }
        }             
     }

что приводит к следующему выводу

VALUE1: class Test$1                                                                                                                                                                               
        public static final java.lang.String Test$1.CONST_RELATED_TO_VALUE1                                                                                                                
        public static final java.lang.String Test$1.OTHER_CONST_RELATED_TO_VALUE1                                                                                                          
VALUE2: class Test$2                                                                                                                                                                               
        public static final java.lang.String Test$2.CONST_RELATED_TO_VALUE2                                                                                                                
VALUE3: class Test                                                                                                                                                                                 
        public static final Test Test.VALUE1                                                                                                                                    
        public static final Test Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    
        private static final Test[] Test.$VALUES

Понятно, что у нас есть фактически выделенные подклассы во время выполнения для VALUE1 и VALUE2. Но это также похоже на то, что мы теряем конкретную информацию о типе VALUE1 и VALUE2, так как компилятор генерирует значения статического enum для базового класса enum Test, как используется VALUE3: все члены имеют тип Test и конкретные типы отбрасываются.

Однако мне кажется, что если компилятор просто сохранил такие типы

        public static final Test$1 Test.VALUE1                                                                                                                                    
        public static final Test$2 Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    

весь окружающий код все равно будет работать. Кроме того, мы могли бы также сделать то, что я попробовал вначале, и получить доступ CONST_RELATED_TO_VALUE1 через Test.VALUE1, который теперь явно является экземпляром типа Test$1, а не только Test, и, хотя его вообще следует избегать, кажется, в этом случае отлично подходит для доступа к этому статическому элементу через экземпляр.

Теперь, как многие люди правильно указали, использование анонимных классов слева недействительно Java-кода и, возможно, также для компилятора не допускается без какого-либо значительного изменения спецификации. Однако это можно было легко решить, используя именованные внутренние классы, поэтому мы имели бы

        public static final Test.Value1 Test.VALUE1                                                                                                                                    
        public static final Test.Value2 Test.VALUE2                                                                                                                                    
        public static final Test Test.VALUE3                                                                                                                                    

Это даже дает дополнительное преимущество для отладки, что имя внутреннего подкласса четко отображает соответствующее значение перечисления.

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

Итак, мне было интересно, почему это не было реализовано, как за исключением времени? Я пропустил что-то важное здесь, что помешало бы компилятору сделать это (как по сложности реализации, так и по невозможности системы типов), просто ли это не было реализовано, чтобы упростить его, потому что не было времени или что-то в этом направлении?

(Я в основном искал причины, по которым было принято решение реализовать это с точки зрения компилятора/типа, а не для практических альтернатив этому, так как есть определенная пара, хотя она все еще кажется приятной шаблон)

Ответы

Ответ 1

Статические элементы, такие как CONST_RELATED_TO_VALUE1, являются членами анонимного класса для соответствующего значения перечисления, но не являются членами самого класса enum. Как и в случае с другими анонимными классами, объект VALUE1 здесь объявлен как тип типа Test, хотя он является экземпляром анонимного подкласса Test.

Следовательно, вы не можете получить доступ к CONST_RELATED_TO_VALUE1 через VALUE1.CONST_RELATED_TO_VALUE1, потому что VALUE1 является ссылкой типа Test и CONST_RELATED_TO_VALUE1 является членом анонимного подкласса, но не Test. CONST_RELATED_TO_VALUE1 могут быть доступны только другим членам анонимного класса.

Если вы хотите получить доступ к значениям, определенным в анонимном классе для VALUE1, вам нужно иметь метод (скажем, m()) типа перечисления, который вы переопределите в анонимном классе для объекта enum, и что возвращает или предоставляет как-то значение, которое вы хотите открыть (через VALUE1.m()).

Ответ 2

Константа перечисления - это специальные переменные, которые относятся к типу объявления типа перечисления. Другими словами, ссылочное выражение Test.VALUE1 имеет тип Test. Тип Test не определяет имя переменной CONST_RELATED_TO_VALUE1, поэтому вы не можете получить доступ к ней.

Это похоже на выполнение

class Parent {
}

class Child extends Parent {
    public Object field = new Object();
}
...
Parent ref = new Child(); 
System.out.println(ref.field); // compilation error

кроме вашего случая, вы пытаетесь получить доступ к полю static через ссылочное выражение.


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

Ответ 3

VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

- анонимный класс, который расширяет Test enum. В случае таких классов мы можем получить доступ к своим членам (без помощи отражения) только тогда, когда мы делаем это непосредственно после его создания, например:

class Foo{
    public static void main(String[] args) {
        System.out.println(new Foo(){
            public String x = "foo";
        }.x);
    }
}

Но если мы напишем что-то вроде:

Foo f = new Foo(){
    public String x = "foo";
};
System.out.println(f.x);

мы получим ошибку компиляции, так как f имеет тип Foo, который не имеет объявленного члена x.
И это проблема с вашим перечислением. Что вы здесь сделали:

VALUE1() {
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

на самом деле:

public static final Test VALUE1 = new Test(){
//                  ^^^^^^^^^^^
  public static final String CONST_RELATED_TO_VALUE1 = "constant";
  public static final String OTHER_CONST_RELATED_TO_VALUE1 = "constant";
}

так что вы видите VALUE1 (и другие константы перечисления) типа Test, а не Test$1 (имя анонимного класса, заданного компилятором).

Почему тип Test был выбран над Test$1? Вероятно, это связано с тем, что переменные не могут быть объявлены анонимным типом (мы не можем иметь переменную Test$1 foo), и все типы перечислений фактически скомпилированы в простые классы, которые расширяют класс Enum, поэтому те же правила должен применяться для своих полей (констант).