Je ne peux pas utiliser un Enum extrait d'une constante comme paramètre dans une annotation. J'obtiens cette erreur de compilation: "La valeur de l'attribut d'annotation [attribut] doit être une expression constante enum".
Il s'agit d'une version simplifiée du code pour l'énumération:
public enum MyEnum {
Apple, ORANGE
}
Pour l'annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface MyAnnotation {
String theString();
int theInt();
MyEnum theEnum();
}
Et la classe:
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() {
}
}
L'erreur n'apparaît que dans "theEnum = MYENUM_CONSTANT" sur la méthodeB. Les constantes string et int sont correctes avec le compilateur, la constante Enum ne l'est pas, même si c'est exactement la même valeur que celle de la méthode A. Il me semble que c'est une fonctionnalité manquante dans le compilateur, car les trois sont évidemment des constantes. Il n'y a pas d'appels de méthode, pas d'utilisation étrange de classes, etc.
Ce que je veux réaliser, c'est:
Tout moyen d'atteindre ces objectifs serait parfait.
Modifier:
Merci a tous. Comme vous le dites, cela ne peut pas être fait. Le JLS doit être mis à jour. J'ai décidé d'oublier les énumérations dans les annotations cette fois et d'utiliser des constantes int régulières. Tant que l'int est attribué à partir d'une constante nommée, les valeurs sont bornées et son type est "sûr".
Cela ressemble à ceci:
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() {
...
Et je peux utiliser MYENUMSIMUL_CONSTANT n'importe où ailleurs dans le code.
Il semble être défini dans le JLS # 9.7.1 :
[...] Le type de V est compatible d'affectation (§5.2) avec T, et de plus:
- [...]
- Si T est un type enum et V est une constante enum.
Et une constante d'énumération est définie comme la constante d'énumération réelle ( JLS # 8.9.1 ), pas une variable qui pointe vers cette constante.
Conclusion: si vous souhaitez utiliser une énumération comme paramètre pour votre annotation, vous devrez lui donner une valeur explicite MyEnum.XXXX
. Si vous souhaitez utiliser une variable, vous devrez choisir un autre type (pas une énumération).
Une solution de contournement possible consiste à utiliser un String
ou int
que vous pouvez ensuite mapper à votre énumération - vous perdrez la sécurité du type mais les erreurs peuvent être facilement repérées lors de l'exécution (= pendant les tests).
"Tous les problèmes en informatique peuvent être résolus par un autre niveau d'indirection" --- David Wheeler
C'est ici:
Classe d'énumération:
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";
}
}
Classe de personne:
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 {
...
}
Je pense que la réponse la plus votée est incomplète, car cela ne garantit pas du tout que la valeur enum est couplée avec la constante sous-jacente String
value . Avec cette solution, il suffit de découpler les deux classes.
Au lieu de cela, je suggère plutôt de renforcer le couplage montré dans cette réponse en imposant la corrélation entre le nom de l'énumération et la valeur constante comme suit:
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";
}
}
Comme indiqué par @ GhostCat dans un commentaire, des tests unitaires appropriés doivent être mis en place pour assurer le couplage.
La règle de contrôle semble être "Si T est un type enum et V est une constante enum.", 9.7.1. Annotations normales . D'après le texte, il semble que le JLS vise une évaluation extrêmement simple des expressions dans les annotations. Une constante enum est spécifiquement l'identifiant utilisé dans la déclaration enum.
Même dans d'autres contextes, une finale initialisée avec une constante enum ne semble pas être une expression constante. 4.12.4. Variables finales dit "Une variable de type primitif ou de type String, qui est finale et initialisée avec une expression constante au moment de la compilation (§15.28), est appelée une variable constante.", Mais n'inclut pas une finale de type enum initialisée avec une constante enum.
J'ai également testé un cas simple dans lequel il importe si une expression est une expression constante - un si entourant une affectation à une variable non affectée. La variable n'est pas devenue affectée. Une version alternative du même code qui a plutôt testé un entier final a rendu la variable définitivement attribuée:
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
}
}
Je cite la dernière ligne de la question
Tout moyen d'atteindre ces objectifs serait parfait.
Alors j'ai essayé ça
Ajout d'un paramètre enumType à l'annotation en tant qu'espace réservé
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface MyAnnotation {
String theString();
int theInt();
MyAnnotationEnum theEnum() default MyAnnotationEnum.Apple;
int theEnumType() default 1;
}
Ajout d'une méthode getType dans l'implémentation
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;
}
}
Apporté une modification pour utiliser une constante int au lieu de l'énumération
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() {
}
}
Je dérive la constante MYENUM de MYENUM_TYPE int, donc si vous changez MYENUM, il vous suffit de changer la valeur int en la valeur de type enum correspondante.
Ce n'est pas la solution la plus élégante, mais je la donne à cause de la dernière ligne de la question.
Tout moyen d'atteindre ces objectifs serait parfait.
Juste une remarque, si vous essayez d'utiliser
public static final int MYENUM_TYPE = MyAnnotationEnum.Apple.type;
Le compilateur dit à l'annotation- MyAnnotation.theEnumType doit être une constante
Ma solution était
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();
}
Je pensais que c'était la façon la plus propre. Cela répond à quelques exigences:
@Annotation(foo = MyEnum._FOO)
MODIFIER
Cela conduit parfois à une erreur de compilation, ce qui conduit à la raison de l'original element value must be a constant expression
Ce n'est donc apparemment pas une option!