定数から取得した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() {
}
}
このエラーは、methodBの「theEnum = MYENUM_CONSTANT」でのみ表示されます。コンパイラでは文字列定数とint定数は問題ありませんが、Enum定数はmethodAの値とまったく同じ値ですが、そうではありません。 3つすべてが明らかに定数であるため、これはコンパイラに欠けている機能のように見えます。メソッド呼び出し、クラスの奇妙な使用などはありません。
私が達成したいのは:
これらの目標を達成するための方法は問題ありません。
編集:
皆さんありがとう。あなたが言うように、それはできません。 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を使用できます。
JLS#9.7.1 で定義されているようです:
[...] Vのタイプは、Tと割り当て互換(§5.2)であり、さらに:
- [...]
- Tが列挙型で、Vが列挙定数である場合。
また、列挙定数は、その定数を指す変数ではなく、実際の列挙定数( JLS#8.9.1 )として定義されます。
結論:注釈のパラメーターとして列挙型を使用する場合は、明示的なMyEnum.XXXX
値を指定する必要があります。変数を使用する場合は、列挙型ではなく別の型を選択する必要があります。
可能な回避策の1つは、列挙型にマップできるString
またはint
を使用することです。型安全性は失われますが、実行時にエラーを簡単に見つけることができます(=テスト中)。
「コンピュータサイエンスのすべての問題は、別のレベルの間接参照によって解決できます」--- David Wheeler
ここにあります:
列挙型クラス:
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 {
...
}
最も投票された答え は不完全であると思います、なぜならそれは列挙値が基礎となる定数String
value。このソリューションでは、2つのクラスを分離するだけです。
代わりに、次のように列挙名と定数値の間の相関を強制することにより、その答えに示されている結合を強化することをお勧めします。
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 が指摘しているように、適切な単体テストを実施して、結合を確認する必要があります。
制御規則は「Tが列挙型で、Vが列挙定数の場合」、 9.7.1。Normal Annotations のようです。テキストから、JLSは注釈内の式の非常に単純な評価を目指しているようです。列挙定数は、具体的には列挙宣言内で使用される識別子です。
他のコンテキストでも、enum定数で初期化されたfinalは定数式ではないようです。 4.12.4。final Variables は、「プリミティブ型またはString型の変数、finalでコンパイル時の定数式(§15.28)で初期化された変数は定数変数と呼ばれます。」と言いますが、 enum定数で初期化されたenumタイプのfinalは含まれません。
また、式が定数式であるかどうかが重要な単純なケース、つまり未割り当て変数への割り当てを囲むifをテストしました。変数は割り当てられませんでした。最終的な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
}
}
質問の最後の行から引用します
これらの目標を達成するための方法は問題ありません。
だから私はこれを試した
プレースホルダーとして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は定数でなければなりません
私の解決策は
_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)
[〜#〜] edit [〜#〜]
これにより、コンパイルエラーが発生することがあり、元の_element value must be a constant expression
_の原因になります。
したがって、これは明らかにオプションではありません!