Как указать значение 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 где-нибудь еще в коде.
Ответы
Ответ 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
Так что это, видимо, не вариант!