有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

如何在Java中从常量向注释提供枚举值

我无法将从常量中获取的枚举用作批注中的参数。我得到这个编译错误:“注释属性[attribute]的值必须是枚举常量表达式”

这是枚举代码的简化版本:

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_常量”中显示。字符串和int常量在编译器中是可以接受的,枚举常量则不是,即使它的值与methodA上的值完全相同。在我看来,这是编译器中缺少的功能,因为这三个显然都是常量。没有方法调用,没有类的奇怪用法,等等

我想要实现的是:

  • 在注释和以后的代码中使用MYENUM_常量
  • 保持类型安全

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

编辑:

谢谢大家。正如你所说,这是做不到的。应更新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_常量


共 (6) 个答案

  1. # 1 楼答案

    我认为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在评论中指出的,必须进行适当的单元测试以确保耦合

  2. # 2 楼答案

    我的解决方案是

    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

    所以这显然不是一个选项

  3. # 3 楼答案

    它似乎是在JLS #9.7.1中定义的:

    [...] The type of V is assignment compatible (§5.2) with T, and furthermore:

    • [...]
    • If T is an enum type, and V is an enum constant.

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

    一句话:如果你想使用一个枚举作为注解的参数,你需要给它一个显式的MyEnum.XXXX值。如果要使用变量,则需要选择另一种类型(而不是枚举)

    一种可能的解决方法是使用Stringint映射到枚举,这样会失去类型安全性,但在运行时(=测试期间)很容易发现错误

  4. # 4 楼答案

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

    即使在其他上下文中,用enum常量初始化的final似乎也不是常量表达式4.12.4. final Variables说“一个原语类型或字符串类型的变量,它是最终的,并用编译时常量表达式(§15.28)初始化,称为常量变量。”,但不包括使用枚举常量初始化的枚举类型的最终值

    我还测试了一个简单的例子,在这个例子中,一个表达式是否是常量表达式很重要——一个围绕未赋值变量赋值的if。变量没有被赋值。测试最终整数的同一代码的另一个版本确实明确指定了变量:

      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. # 5 楼答案

    我引用问题最后一行的话

    Any way to achieve these goals would be fine.

    所以我试过这个

    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值更改为相应的枚举类型值

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

    Any way to achieve these goals would be fine.

    如果你尝试使用

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

    编译器在注释处显示-MyAnnotation。numtype必须是常量

  6. # 6 楼答案

    “计算机科学中的所有问题都可以通过另一种间接方式来解决”——大卫·惠勒

    这是:

    枚举类:

    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 {
    ...
    }