ChatGPT解决这个技术问题 Extra ChatGPT

如何从 Java 中的常量向注解提供枚举值

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


I
Ivan Hristov

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

这里是:

枚举类:

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}) 您还不如使用只有常量的接口或类。
大声笑,我需要这个的确切用例和 json 注释。诡异的。
J
JeanValjean

我认为 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 应用程序时,它第一次运行时进行检查并没有帮助;-)
L
Lii

它似乎在 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/… 中的答案提出了一种紧凑且安全的解决方案
P
Patricia Shanahan

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

即使在其他情况下,使用枚举常量初始化的 final 似乎也不是常量表达式。 4.12.4. final Variables 表示“原始类型或字符串类型的变量,它是 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==3) with za static final int 在常量表达式列表中。我会用一些编译器检查它,看看我能找到什么。
A
Aditya

我引用问题的最后一行

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

所以我尝试了这个

在注解中添加了一个 enumType 参数作为占位符 @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface MyAnnotation { String theString(); int theInt(); MyAnnotationEnum theEnum() 默认 MyAnnotationEnum.APPLE; int theEnumType() 默认 1;在实现 public enum MyAnnotationEnum { APPLE(1), ORANGE(2); 中添加了一个 getType 方法公共最终 int 类型; private MyAnnotationEnum(int type) { this.type = type; } public final int getType() { 返回类型; } public static MyAnnotationEnum getType(int type) { if (type == APPLE.getType()) { return APPLE; } else if (type == ORANGE.getType()) { return ORANGE; } 返回苹果; } } 更改为使用 int 常量而不是枚举 public class MySample { public static final String STRING_CONSTANT = "hello";公共静态最终 int INT_CONSTANT = 1; public static final int MYENUM_TYPE = 1;//MyAnnotationEnum.APPLE.type;公共静态最终 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 的答案标记为已接受,并用我最终做的事情编辑了我的问题,这只是“某种”类型的安全。
D
DKo

我的解决方案是

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的原因

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