Подтвердить что ты не робот

Как указать значение Enum для аннотации из константы в Java

Я не могу использовать Enum, взятый из константы в качестве параметра в аннотации. Я получаю эту ошибку компиляции: "Значение атрибута аннотации [attribute] должно быть постоянным выражением enum".

Это упрощенная версия кода для Enum:

public enum MyEnum {
    APPLE, ORANGE
}

Для аннотации:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface MyAnnotation {
    String theString();

    int theInt();

    MyEnum theEnum();
}

И класс:

public class Sample {
    public static final String STRING_CONSTANT = "hello";
    public static final int INT_CONSTANT = 1;
    public static final MyEnum MYENUM_CONSTANT = MyEnum.APPLE;

    @MyAnnotation(theEnum = MyEnum.APPLE, theInt = 1, theString = "hello")
    public void methodA() {

    }

    @MyAnnotation(theEnum = MYENUM_CONSTANT, theInt = INT_CONSTANT, theString = STRING_CONSTANT)
    public void methodB() {

    }

}

Ошибка появляется только в "theEnum = MYENUM_CONSTANT" по методу B. Строковые и int-константы в порядке с компилятором, константа Enum - нет, хотя это то же самое значение, что и метод overA. Мне кажется, что это недостающая особенность в компиляторе, потому что все три являются, очевидно, константами. Нет вызовов методов, странного использования классов и т.д.

Чего я хочу достичь:

  • Чтобы использовать MYENUM_CONSTANT как в аннотации, так и позже в коде.
  • Чтобы сохранить тип безопасности.

Любой способ достичь этих целей будет в порядке.

Edit:

Спасибо всем. Как вы говорите, это невозможно. JLS необходимо обновить. На этот раз я решил забыть о перечислениях в аннотации и использовать регулярные int-константы. Пока int назначается из именованной константы, значения ограничены и безопасны типа "тип".

Он выглядит следующим образом:

public interface MyEnumSimulation {
    public static final int APPLE = 0;
    public static final int ORANGE = 1;
}
...
public static final int MYENUMSIMUL_CONSTANT = MyEnumSimulation.APPLE;
...
@MyAnnotation(theEnumSimulation = MYENUMSIMUL_CONSTANT, theInt = INT_CONSTANT, theString = STRING_CONSTANT)
public void methodB() {
...

И я могу использовать MYENUMSIMUL_CONSTANT где-нибудь еще в коде.

4b9b3361

Ответ 1

Кажется, это определено в JLS # 9.7.1:

[...] Тип V совместим по присваиванию (§5.2) с T, и, кроме того:

  • [...]
  • Если T является типом перечисления, а V является константой перечисления.

И константа перечисления определяется как фактическая константа перечисления (JLS # 8.9.1), а не переменная, которая указывает на эту константу.

Итог: если вы хотите использовать перечисление в качестве параметра для аннотации, вам нужно будет дать ему явное значение MyEnum.XXXX. Если вы хотите использовать переменную, вам нужно будет выбрать другой тип (не enum).

Один из возможных обходных путей - использовать String или int, которые затем можно сопоставить с вашим перечислением - вы потеряете безопасность типов, но ошибки могут быть легко обнаружены во время выполнения (= во время тестов).

Ответ 2

"Все проблемы в информатике могут быть решены другим уровнем косвенности" --- Дэвид Уилер

Вот он:

Класс enum:

public enum Gender {
    MALE(Constants.MALE_VALUE), FEMALE(Constants.FEMALE_VALUE);

    Gender(String genderString) {
    }

    public static class Constants {
        public static final String MALE_VALUE = "MALE";
        public static final String FEMALE_VALUE = "FEMALE";
    }
}

Класс персонажа:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = Person.GENDER)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Woman.class, name = Gender.Constants.FEMALE_VALUE),
    @JsonSubTypes.Type(value = Man.class, name = Gender.Constants.MALE_VALUE)
})
public abstract class Person {
...
}

Ответ 3

Я думаю, что ответ с наибольшим количеством голосов является неполным, поскольку он совсем не гарантирует, что значение перечисления связано с лежащим в основе константным значением String. С этим решением нужно просто отделить два класса.

Вместо этого я скорее предлагаю усилить связь, показанную в этом ответе, установив корреляцию между именем перечисления и значением константы следующим образом:

public enum Gender {
    MALE(Constants.MALE_VALUE), FEMALE(Constants.FEMALE_VALUE);

    Gender(String genderString) {
      if(!genderString.equals(this.name()))
        throw new IllegalArgumentException();
    }

    public static class Constants {
        public static final String MALE_VALUE = "MALE";
        public static final String FEMALE_VALUE = "FEMALE";
    }
}

Как отметил @GhostCat в комментарии, для обеспечения связи должны быть проведены надлежащие модульные тесты.

Ответ 4

Управляющее правило выглядит так: "Если T - тип перечисления, а V - константа перечисления", 9.7.1. Нормальные аннотации. Из текста видно, что JLS стремится к чрезвычайно простой оценке выражений в аннотациях. Константа перечисления - это, в частности, идентификатор, используемый внутри объявления перечисления.

Даже в других контекстах конечная инициализация с константой enum не является постоянным выражением. 4.12.4. final Variables говорит: "Переменная примитивного типа или типа String, которая является окончательной и инициализирована выражением константы времени компиляции (§15.28), называется постоянной переменной.", но не включает окончание перечисления тип инициализируется константой перечисления.

Я также проверил простой случай, в котором имеет значение, является ли выражение постоянным выражением - a, если окружение присваивается неназначенной переменной. Переменная не назначалась. Альтернативная версия того же кода, которая тестировала окончательный int, сделала определенную переменную:

  public class Bad {

    public static final MyEnum x = MyEnum.AAA;
    public static final int z = 3;
    public static void main(String[] args) {
      int y;
      if(x == MyEnum.AAA) {
        y = 3;
      }
  //    if(z == 3) {
  //      y = 3;
  //    }
      System.out.println(y);
    }

    enum MyEnum {
      AAA, BBB, CCC
    }
  }

Ответ 5

Я цитирую из последней строки в вопросе

Любой способ достичь этих целей будет в порядке.

Итак, я попробовал это

  • Добавлен параметр enumType для аннотации в качестве заполнителя

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    public @interface MyAnnotation {
    
        String theString();
        int theInt();
        MyAnnotationEnum theEnum() default MyAnnotationEnum.APPLE;
        int theEnumType() default 1;
    }
    
  • Добавлен метод getType в реализации

    public enum MyAnnotationEnum {
        APPLE(1), ORANGE(2);
        public final int type;
    
        private MyAnnotationEnum(int type) {
            this.type = type;
        }
    
        public final int getType() {
            return type;
        }
    
        public static MyAnnotationEnum getType(int type) {
            if (type == APPLE.getType()) {
                return APPLE;
            } else if (type == ORANGE.getType()) {
                return ORANGE;
            }
            return APPLE;
        }
    }
    
  • Сделано изменение для использования константы int вместо перечисления

    public class MySample {
        public static final String STRING_CONSTANT = "hello";
        public static final int INT_CONSTANT = 1;
        public static final int MYENUM_TYPE = 1;//MyAnnotationEnum.APPLE.type;
        public static final MyAnnotationEnum MYENUM_CONSTANT = MyAnnotationEnum.getType(MYENUM_TYPE);
    
        @MyAnnotation(theEnum = MyAnnotationEnum.APPLE, theInt = 1, theString = "hello")
        public void methodA() {
        }
    
        @MyAnnotation(theEnumType = MYENUM_TYPE, theInt = INT_CONSTANT, theString = STRING_CONSTANT)
        public void methodB() {
        }
    
    }
    

Я получаю константу MYENUM из MYENUM_TYPE int, поэтому, если вы меняете MYENUM, вам просто нужно изменить значение int на соответствующее значение типа перечисления.

Это не самое элегантное решение, но я даю его из-за последней строки в вопросе.

Любой способ достичь этих целей будет в порядке.

Просто обратите внимание, если вы попытаетесь использовать

public static final int MYENUM_TYPE = MyAnnotationEnum.APPLE.type;

Компилятор говорит в аннотации MyAnnotation.theEnumType должен быть константой

Ответ 6

Мое решение было

public enum MyEnum {

    FOO,
    BAR;

    // element value must be a constant expression
    // so we needs this hack in order to use enums as
    // annotation values
    public static final String _FOO = FOO.name();
    public static final String _BAR = BAR.name();
}

Я думал, что это был самый чистый путь. Это соответствует нескольким требованиям:

  • Если вы хотите, чтобы перечисления были числовыми
  • Если вы хотите, чтобы перечисления были другого типа
  • Компилятор уведомляет вас, если рефакторин ссылается на другое значение
  • @Annotation(foo = MyEnum._FOO) использования (минус один символ): @Annotation(foo = MyEnum._FOO)

РЕДАКТИРОВАТЬ Это иногда приводит к ошибке компиляции, которая приводит к тому, что исходное element value must be a constant expression

Так что это, видимо, не вариант!