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


76

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

    }

}

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

我想要实现的是:

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


Answers:


24

它似乎在JLS#9.7.1中定义:

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

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

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

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

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


3
JLS这样说,将其标记为答案:无法完成。我希望可以做到。关于解决方法:@gap_j尝试了映射,我也尝试过。但是,要避免“必须保持不变”错误的其他变化而又不增加头痛,这是一个挑战。我编辑了问题以显示最终结果。
user1118312 2012年

113

“计算机科学中的所有问题都可以通过另一种间接解决方案来解决” --- 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
似乎不错-静态导入Gender.Constants。*甚至会更整齐-Magnus
Smith

2
您能否验证您引用的是Gender.Constants.MALE_VALUE?为了回答您的问题-多次测试了该代码。
伊万·赫里斯托夫

4
在您给出的示例中,您引用的是枚举的值,而不是常量。您需要提供一个常量,可以从JLS松散地翻译出来。请参阅此处的其他答案,以获取有关JLS的更多详细信息。
伊万·赫里斯托夫

1
实际上,为了在@RolesAllowed表示法中使用,您必须引用值,而不是枚举。示例:@RolesAllowed({Gender.Constants.MALE_VALUE})这不起作用:@RolesAllowed({Gender.MALE})您最好使用仅包含常量的接口或类。
原子88

1
大声笑,确切的用例和我需要的json注释。奇怪的。
b15

27

我认为投票最多的答案是不完整的,因为它根本无法保证枚举值与基础常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在评论中指出的那样,必须进行适当的单元测试以确保耦合。


3
如果您基于该答案创建自己的答案,那么上一个答案就不会毫无意义。
dantuch

是的,对。最合适的词是“不完整”。
JeanValjean

5
谢谢@JeanValjean,这是宝贵的贡献!(摘自投票率最高的答案的作者)
伊万·赫里斯托夫

2
不确定是否有必要。您已经检查了枚举常量是否与字符串匹配。如果您在枚举名称和原始字符串中有错别字,那么另一个单元测试真的可以帮到您吗?
GhostCat

1
我留给你。也许您应该添加一个单元测试,以确保添加到构造函数的检查在交付代码之前进行。第一次运行是在客户启动Java应用程序时进行检查是没有帮助的;-)
GhostCat

7

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

即使在其他情况下,用枚举常量初始化的final似乎也不是常量表达式。4.12.4。final变量说:“原始类型或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
    }
  }

是的,看起来确实像“ JLS旨在对注释中的表达式进行极其简单的评估”。关于代码,按原样运行时得到“ 3”。从文本看来,您在MyEnum中没有得到“ 3”,而在(注释过的)“ z”中得到了3。你能澄清一下吗?
user1118312 2012年

这很有趣-看起来编译器对此有所不同。注释掉的版本应该起作用,因为具有za static final int的(z == 3)在常量表达式列表上。我将使用一些编译器对其进行检查,然后看看可以找到什么。
Patricia Shanahan 2012年

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() {
        }
    
    }
    

我从MYENUM_TYPE int派生MYENUM常量,因此,如果更改MYENUM,则只需将int值更改为相应的枚举类型值。

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

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

只是附带说明,如果您尝试使用

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

编译器在注解中说MyAnnotation.theEnumType必须是一个常量


也可参考此答案了类似的问题
阿迪亚

1
谢谢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)公共无效方法B(){...
user1118312

我认为问题不能在编译时解决。使用您提供的方法会引发运行时错误java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: 2
Aditya 2012年

嗯 您的堆栈跟踪记录中提到了“ 2”,而我键入的示例中没有“ 2”。在示例中使用“ 0”并使用原始枚举(而不是带有构造函数和方法的枚举),其行为类似于您的代码。没有异常被抛出。我将@assylias答案标记为已接受,并用我最终所做的事情来编辑我的问题,这只是“某种”类型的安全。
user1118312

-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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.