如何从Java中的常量为注释提供枚举值

时间:2012-11-06 14:56:53

标签: java enums annotations

我无法使用从常量中取得的枚举作为注释中的参数。我得到这个编译错误:“注释属性[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() {

    }

}

错误仅出现在methodB上的“theEnum = MYENUM_CONSTANT”中。字符串和int常量对编译器来说是可以的,但枚举常量不是,即使它与方法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。

6 个答案:

答案 0 :(得分:80)

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

答案 1 :(得分:20)

似乎在JLS #9.7.1

中定义
  

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

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

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

底线:如果您想使用枚举作为注释的参数,则需要为其指定明确的MyEnum.XXXX值。如果要使用变量,则需要选择其他类型(不是枚举)。

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

答案 2 :(得分:16)

我认为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";
    }
}

此外,应编写单元测试,检查字符串是否为常量。

答案 3 :(得分:7)

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

即使在其他情况下,使用枚举常量初始化的最终值似乎也不是常量表达式。 4.12.4. final Variables表示“一个原始类型或类型String的变量,它是最终的并用编译时常量表达式(第15.28节)初始化,称为常量变量。”,但不包括枚举类型的最后一个用枚举常量初始化。

我还测试了一个简单的例子,其中表达式是否是一个常量表达式 - 如果围绕对未赋值变量的赋值。该变量未被分配。测试最终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
    }
  }

答案 4 :(得分:2)

我引用问题的最后一行

  

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

所以我试过这个

  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() {
        }
    
    }
    
  4. 我从MYENUM_TYPE int派生MYENUM常量,所以如果你更改MYENUM,你只需要将int值更改为相应的枚举类型值。

    它不是最优雅的解决方案,但是由于问题的最后一行,我正在给它。

      

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

    如果您尝试使用

    ,请注意
    public static final int MYENUM_TYPE = MyAnnotationEnum.APPLE.type;
    

    编译器在注释中指出 - MyAnnotation.theEnumType必须是常量

答案 5 :(得分:0)

我的解决方法是

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

的原因

因此,这显然不是一种选择!