我无法使用从常量中获取的枚举作为注释中的参数。我收到此编译错误:"注释属性[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。
相关:stackoverflow.com/questions/3271659/
"计算机科学中的所有问题都可以通过另一种间接解决方案来解决"-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。*甚至更整洁
既然这根本行不通,那就从来没有测试过,不是吗?
您能说明什么不起作用吗?
这样做,我仍然收到错误消息:注释属性xxx.yyy的值必须是一个常量表达式
您能否验证您引用的是Gender.Constants.MALE_VALUE?为了回答您的问题-该代码已多次测试。
由于时间太长,请查看stackoverflow.com/questions/27525746/
在您给出的示例中,您引用的是枚举值,而不是常量。您需要提供一个常量,可以从JLS松散地翻译出来。请参阅此处的其他答案,以获取有关JLS的更多详细信息。
辉煌!为我工作了!
这个家伙,他真是个天才。 #问题解决了。
实际上,为了使用@RolesAllowed表示法,您必须引用值,而不是枚举。示例:@RolesAllowed({Gender.Constants.MALE_VALUE})这不起作用:@RolesAllowed({Gender.MALE})您最好使用仅包含常量的接口或类。
@ atom88,您好,这对示例是非常有价值的贡献。您是否想完成它(可能添加一句话,包括安全性JEE的注释和用例等)还是应该?谢谢!
@IvanHristov,请继续添加示例。
大声笑,确切的用例和json注释,我需要这样做。奇怪的。
我认为投票最多的答案是不完整的,因为它根本无法保证枚举值与基础常量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,这是宝贵的贡献! (摘自投票率最高的答案的作者)
不错的附录。 但是应该编写单元测试来检查字符串是否是常量。 是没有必要的。 当您使用不为常量的String时,编译器会费劲。 是的,即使将其声明为公共静态最终形式,计算机也会阻止您执行SOME_OTHER_CONSTANT.toString()。
@GhostCat我的意思是单元测试应该断言所定义的字符串与期望值匹配。
不知道是否有必要。 您已经检查了枚举常量是否与字符串匹配。 如果您在枚举名称和原始字符串中有错别字,那么另一个单元测试真的可以帮到您吗?
@GhostCat没错。 我想你是对的! 您想更新答案吗?
我留给你。 也许您应该补充一点,应该使用单元测试来确保添加到构造函数的检查在交付代码之前进行。 第一次运行是在客户启动Java应用程序时进行检查是没有帮助的;-)
它似乎在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值。如果要使用变量,则需要选择其他类型(而不是枚举)。
一种可能的解决方法是使用String或int,然后可以将其映射到您的枚举-您将松开类型安全性,但可以在运行时轻松发现错误(=在测试期间)。
JLS这样说,将其标记为答案:无法完成。我希望可以做到。关于解决方法:@gap_j尝试了映射,我也尝试过。但是,避免"必须保持不变"错误的其他变体而又不增加头痛被证明是一个挑战。我编辑了问题以显示最终结果。
控制规则似乎是"如果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。你能澄清一下吗?
这很有趣-看起来编译器对此有所不同。注释掉的版本应该起作用,因为(z == 3)带有z的静态最终int在常量表达式列表中。虐待检查一些编译器,看看我能找到什么。
我引用问题的最后一行
Any way to achieve these goals would be fine.
所以我尝试了这个
在注释中添加了一个enumType参数作为占位符
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface MyAnnotation {
String theString();
int theInt();
MyAnnotationEnum theEnum() default MyAnnotationEnum.APPLE;
int theEnumType() default 1;
}
在实现中添加了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;
}
}
进行了更改,以使用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.theEnumType必须是一个常量
也请参考此答案以获取类似问题
谢谢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(){...
我认为问题不能在编译时解决。使用您提供的方法会引发运行时错误java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: 2
嗯您的堆栈跟踪记录中提到" 2",而我键入的示例中没有" 2"。在示例中使用" 0",并使用原始枚举(而不是带有构造函数和方法的枚举),其行为类似于您的代码。没有异常被抛出。我将@assylias答案标记为已接受,并使用最终完成的操作来编辑我的问题,这只是"某种"类型的安全。
我的解决方案是
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的原因
因此,这显然不是一个选择!