web-dev-qa-db-ja.com

Javaの定数から注釈にEnum値を提供する方法

定数から取得した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つすべてが明らかに定数であるため、これはコンパイラに欠けている機能のように見えます。メソッド呼び出し、クラスの奇妙な使用などはありません。

私が達成したいのは:

  • 注釈と後のコードの両方でMYENUM_CONSTANTを使用します。
  • タイプセーフを維持するため。

これらの目標を達成するための方法は問題ありません。

編集:

皆さんありがとう。あなたが言うように、それはできません。 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を使用できます。

62
user1118312

JLS#9.7.1 で定義されているようです:

[...] Vのタイプは、Tと割り当て互換(§5.2)であり、さらに:

  • [...]
  • Tが列挙型で、Vが列挙定数である場合。

また、列挙定数は、その定数を指す変数ではなく、実際の列挙定数( JLS#8.9.1 )として定義されます。

結論:注釈のパラメーターとして列挙型を使用する場合は、明示的なMyEnum.XXXX値を指定する必要があります。変数を使用する場合は、列挙型ではなく別の型を選択する必要があります。

可能な回避策の1つは、列挙型にマップできるStringまたはintを使用することです。型安全性は失われますが、実行時にエラーを簡単に見つけることができます(=テスト中)。

20
assylias

「コンピュータサイエンスのすべての問題は、別のレベルの間接参照によって解決できます」--- 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 {
...
}
86
Ivan Hristov

最も投票された答え は不完全であると思います、なぜならそれは列挙値が基礎となる定数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 が指摘しているように、適切な単体テストを実施して、結合を確認する必要があります。

19
JeanValjean

制御規則は「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
    }
  }
7

質問の最後の行から引用します

これらの目標を達成するための方法は問題ありません。

だから私はこれを試した

  1. プレースホルダーとしてenumTypeパラメーターを注釈に追加しました

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    public @interface MyAnnotation {
    
        String theString();
        int theInt();
        MyAnnotationEnum theEnum() default MyAnnotationEnum.Apple;
        int theEnumType() default 1;
    }
    
  2. 実装に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;
        }
    }
    
  3. 列挙の代わりに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は定数でなければなりません

2
Aditya

私の解決策は

_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();
}
_

これが一番きれいな方法だと思いました。これはいくつかの要件を満たしています。

  • 列挙型を数値にする場合
  • 列挙型を他のタイプにする場合
  • リファクタリングが別の値を参照する場合、コンパイラは通知します
  • 最もクリーンなユースケース(マイナス1文字):@Annotation(foo = MyEnum._FOO)

[〜#〜] edit [〜#〜]

これにより、コンパイルエラーが発生することがあり、元の_element value must be a constant expression_の原因になります。

したがって、これは明らかにオプションではありません!

0
DKo