【问题标题】:How to supply Enum value to an annotation from a Constant in Java如何从 Java 中的常量向注解提供枚举值
【发布时间】:2012-10-26 13:24:50
【问题描述】:

我无法使用取自常量的枚举作为注释中的参数。我收到此编译错误:“注释属性 [attribute] 的值必须是枚举常量表达式”。

这是 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() {

    }

}

该错误仅出现在方法 B 上的“theEnum = MYENUM_CONSTANT”中。编译器可以使用 String 和 int 常量,但 Enum 常量不行,即使它与方法 A 上的值完全相同。在我看来这是编译器中缺少的功能,因为这三个显然都是常量。没有方法调用,没有奇怪的类使用等等。

我想要实现的是:

  • 在注释和后面的代码中都使用 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。

【问题讨论】:

标签: java enums annotations


【解决方案1】:

我的解决方案是

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)

编辑

这样偶尔会导致编译错误,导致原来element value must be a constant expression的原因

所以这显然不是一个选择!

【讨论】:

    【解决方案2】:

    我认为the most voted answer 是不完整的,因为它根本不能保证枚举值与底层常量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 在评论中指出的那样,必须进行适当的单元测试以确保耦合。

    【讨论】:

    • 上一个答案不可能毫无意义,如果您根据那个答案创建自己的答案。
    • 是的。最合适的词是“不完整”。
    • 谢谢@JeanValjean,这是一个宝贵的贡献! (来自投票最多答案的作者)
    • 不确定是否有必要。您已经检查了枚举常量是否与字符串匹配。如果枚举名称和原始字符串中有拼写错误,那么另一个单元测试真的有帮助吗?
    • 我把它留给你。也许您应该补充一点,应该使用单元测试来确保您添加到构造函数的检查发生在发送代码之前。当您的客户启动您的 Java 应用程序时,它第一次运行时进行检查并没有帮助;-)
    【解决方案3】:

    好像定义在JLS #9.7.1:

    [...] V 的类型与 T 的赋值兼容(第 5.2 节),此外:

    • [...]
    • 如果 T 是枚举类型,而 V 是枚举常量。

    枚举常量被定义为实际的枚举常量 (JLS #8.9.1),而不是指向该常量的变量。

    底线:如果你想使用枚举作为注释的参数,你需要给它一个明确的MyEnum.XXXX 值。如果要使用变量,则需要选择另一种类型(不是枚举)。

    一种可能的解决方法是使用Stringint,然后您可以将其映射到您的枚举 - 您将失去类型安全性,但可以在运行时轻松发现错误(= 在测试期间)。

    【讨论】:

    • 将此标记为答案:无法完成,JLS 这么说。我希望它可以做到。关于解决方法:@gap_j 尝试了映射,我也尝试过。但是,在不增加头痛的情况下避免“必须是恒定的”错误的其他变体被证明是一个挑战。我编辑了我的问题以显示我最终做了什么。
    • 形式上这可能是一个正确的答案,但没有真正的解决方案。 OTOH stackoverflow.com/questions/13253624/… 中的答案提出了一个紧凑且安全的解决方案
    【解决方案4】:

    “计算机科学中的所有问题都可以通过另一个层次的间接性来解决” --- 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 {
    ...
    }
    

    【讨论】:

    • 看起来不错 - Gender.Constants.* 的静态导入会更整洁
    • 您能否确认您引用的是 Gender.Constants.MALE_VALUE ?回答您的问题 - 代码经过多次测试。
    • 在您给出的示例中,您引用的是枚举值而不是常量。您需要给出一个常数,这是从 JLS 大致翻译过来的。请在此处查看其他答案以了解有关 JLS 的更多详细信息。
    • 实际上,为了在 @RolesAllowed 表示法中使用,您必须引用该值,而不是枚举。示例:@RolesAllowed({ Gender.Constants.MALE_VALUE }) 这不起作用:@RolesAllowed({ Gender.MALE}) 您不妨使用只有常量的接口或类。
    • LOL,我需要这个的确切用例和 json 注释。很奇怪。
    【解决方案5】:

    我引用问题的最后一行

    任何实现这些目标的方法都可以。

    所以我尝试了这个

    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_TYPE int 派生了 MYENUM 常量,所以如果你更改 MYENUM,你只需要将 int 值更改为对应的枚举类型值。

    它不是最优雅的解决方案,但我给出它是因为问题的最后一行。

    任何实现这些目标的方法都可以。

    只是一个旁注,如果你尝试使用

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

    编译器在注释处说- MyAnnotation.theEnumType 必须是一个常量

    【讨论】:

    • 类似问题也可以参考answer
    • 感谢 gap_j。但它不完全是类型安全的,因为“MYENUM_TYPE”可以采用非法值(即 30)并且编译器不会注意到。我也认为可以通过以下方式在没有额外代码的情况下实现同样的效果: public static final int MYENUM_INT_CONSTANT = 0;公共静态最终 MyEnum MYENUM_CONSTANT = MyEnum.values()[MYENUM_INT_CONSTANT]; ... @MyAnnotation(theEnumSimulation = MYENUM_INT_CONSTANT, theInt = INT_CONSTANT, theString = STRING_CONSTANT) public void methodB() { ...
    • 我不认为这个问题可以在编译时解决。使用您提供的方法会引发运行时错误java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: 2
    • 嗯。您的堆栈跟踪提到了“2”,而我输入的示例中没有“2”。使用示例中的“0”,并使用原始枚举(不是带有构造函数和方法的枚举),它的行为就像您的代码一样。不抛出异常。我将@assylias 的答案标记为已接受,并用我最终做的事情编辑了我的问题,这只是“某种”类型的安全。
    【解决方案6】:

    控制规则似乎是“如果 T 是枚举类型,而 V 是枚举常量。”,9.7.1. Normal Annotations。从文本中可以看出,JLS 的目标是对注释中的表达式进行极其简单的评估。枚举常量具体是枚举声明中使用的标识符。

    即使在其他情况下,使用枚举常量初始化的 final 似乎也不是常量表达式。 4.12.4. final Variables 说“原始类型或 String 类型的变量,它是 final 并使用编译时常量表达式(第 15.28 节)初始化,称为常量变量。”,但不包括使用初始化的枚举类型 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
        }
      }
    

    【讨论】:

    • 是的,它看起来确实像“JLS 的目标是对注释中的表达式进行极其简单的评估”。关于代码,当我按原样运行它时,我得到一个“3”。从文本中可以看出,MyEnum 没有得到“3”,而(注释掉的)“z”得到了 3。你能澄清一下吗?
    • 这很有趣 - 看起来编译器对此有所不同。注释掉的版本应该可以工作,因为带有 z 的 (z==3) 静态最终 int 在常量表达式列表中。我会用一些编译器检查它,看看我能找到什么。
    猜你喜欢
    • 2011-10-27
    • 2017-04-19
    • 2011-07-09
    • 2022-01-18
    • 1970-01-01
    • 2013-03-13
    • 1970-01-01
    • 2014-09-26
    • 1970-01-01
    相关资源
    最近更新 更多