【问题标题】:Enum default value for Java enum annotation valueJava枚举注解值的枚举默认值
【发布时间】:2011-10-27 15:07:23
【问题描述】:

Java 允许 enum 作为注释值的值。如何为enum 注释值定义一种通用的默认enum 值?

我已经考虑了以下,但它不会编译:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public <T extends Enum<T>> @interface MyAnnotation<T> {

    T defaultValue();

}

这个问题有没有解决办法?

赏金

似乎没有针对这种 Java 极端情况的直接解决方案。所以,我开始赏金寻找这个问题的最优雅的解决方案。

理想的解决方案应该理想地满足以下标准:

  1. 一个注释可在所有枚举上重复使用
  2. 从注释实例中检索默认枚举值作为枚举的最小工作量/复杂性

迄今为止最好的解决方案

沙丘:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {

    // By not specifying default,
    // we force the user to specify values
    Class<? extends Enum<?>> enumClazz();
    String defaultValue();

}

...

public enum MyEnumType {
    A, B, D, Q;
}

...

// Usage
@MyAnnotation(enumClazz=MyEnumType.class, defaultValue="A"); 
private MyEnumType myEnumField;

当然,我们不能强制用户在编译时指定一个有效的默认值。但是,任何注释预处理都可以使用valueOf() 验证这一点。

改进

Arian 提供了一个优雅的解决方案来摆脱带注释字段中的clazz

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {

}

...

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@MyAnnotation()
public @interface MyEnumAnnotation {

    MyEnumType value(); // no default has user define default value

}

...

@MyEnumAnnotation(MyEnum.FOO)
private MyEnumType myValue;

注释处理器应在字段上搜索MyEnumAnnotation 以获取提供的默认值。

这需要为每个枚举类型创建一个注解类型,但保证编译时检查类型的安全性。

【问题讨论】:

  • 那不是没有意义吗?每当您在运行时处理注释时,通用信息就会丢失。
  • 这样可以避免我必须为每个枚举类型定义一个注释以便在我的代码中使用(这是一个编译时问题)。
  • 我并不是说这个想法没有用。但是泛型仅在编译时才知道。您已将注释标记为在运行时中保留。但是要访问注释,你必须通过反射——你不会知道泛型最初是什么。
  • 好的,但我对在运行时了解泛型类型不感兴趣。如果构造函数中没有提供对象的枚举字段值(例如),我只想确保在我启动对象的枚举字段值时手头有一个默认值。
  • 您使用注释声明默认值的用例是什么,而您无法使用简单的private MyEnum field = MyEnum.DEFAULT;

标签: java enums annotations default-value


【解决方案1】:

我也有类似的需求,并想出了以下非常简单的解决方案:

实际@Default接口:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Default {}

用法:

public enum Foo {
    A,
    @Default B,
    C;
}

寻找默认值:

public abstract class EnumHelpers {
    public static <T extends Enum<?>> T defaultEnum(Class<T> clazz) {
        Map<String, T> byName = Arrays.asList(clazz.getEnumConstants()).stream()
            .collect(Collectors.toMap(ec -> ec.name(), ec -> ec));

        return Arrays.asList(clazz.getFields()).stream()
             .filter(f -> f.getAnnotation(Default.class) != null)
             .map(f -> byName.get(f.getName()))
             .findFirst()
             .orElse(clazz.getEnumConstants()[0]);
    }   
}

我也尝试过返回 Optional&lt;T&gt;,而不是默认为类中声明的第一个 Enum 常量。

当然,这将是一个类范围的默认声明,但这符合我的需要。 YMMV :)

【讨论】:

    【解决方案2】:

    我不确定你的用例是什么,所以我有两个答案:

    答案 1:

    如果您只想编写尽可能少的代码,我建议您扩展 Dunes' 答案:

    public enum ImplicitType {
        DO_NOT_USE;
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface MyAnnotation {
    
        Class<? extends Enum<?>> clazz() default ImplicitType.class;
    
        String value();
    }
    
    @MyAnnotation("A"); 
    private MyEnumType myEnumField;
    

    clazzImplicitType.class 时,使用字段类型作为枚举类。

    答案 2:

    如果你想做一些框架魔法并想维护编译器检查的类型安全,你可以这样做:

    /** Marks annotation types that provide MyRelevantData */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MyAnnotation {
    }
    

    在客户端代码中,您将拥有

    /** Provides MyRelevantData for TheFramework */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @MyAnnotation
    public @interface MyEnumAnnotation {
    
        MyEnumType value(); // default MyEnumType.FOO;
    
    }
    
    @MyEnumAnnotation(MyEnum.FOO)
    private MyEnumType myValue;
    

    在这种情况下,您将扫描该字段以查找再次使用MyAnnotation 注释的注释。但是,您必须通过注释对象上的反射来访问该值。似乎这种方法在框架方面更复杂。

    【讨论】:

    • 看起来很有趣,但不应该在你的 MyAnnotation 版本中保留 clazz 并且不应该用 @MyAnnotation(clazz=MyEnumType.class) 注释 MyEnumAnnotation?还是我错过了什么?
    • 对于第二种方法,您不需要 clazz 字段,因为您可以直接从客户端注释中获取枚举值。如果还需要值类型,可以使用 value() 函数的返回类型。
    【解决方案3】:

    我的建议类似于kapep's 建议。不同的是我建议使用注解处理器来创建代码。

    一个简单的例子是,如果您打算仅将它用于您自己编写的枚举。用特殊的枚举注解枚举。然后注释处理器将为该枚举生成一个新的注释。

    如果您使用很多不是您编写的枚举,那么您可以实现一些名称映射方案:枚举名称 -> 注释名称。然后,当注释处理器在您的代码中遇到这些枚举之一时,它将自动生成适当的注释。

    您要求:

    1. 一个注释可在所有枚举上重复使用...从技术上讲不是,但我认为效果是一样的。
    2. 从注释实例中检索默认枚举值作为枚举的最小工作量/复杂性...您可以检索默认枚举值而无需任何特殊处理

    【讨论】:

      【解决方案4】:

      使用注解的框架确实可以从使用apt 中获益。它是 javac 中包含的预处理器,可以让您分析声明及其注释(但不能分析方法内的局部声明)。

      对于您的问题,您需要编写一个AnnotationProcessor(用作预处理起点的类)来使用Mirror API 分析注释。实际上 Dunes 的注释非常接近这里需要的。太糟糕的枚举名称不是常量表达式,否则 Dunes 的解决方案会非常好。

      @Retention(RetentionPolicy.SOURCE)
      @Target(ElementType.FIELD)
      public @interface MyAnnotation {
          Class<? extends Enum<?>> clazz();
          String name() default "";
      }
      

      这是一个示例枚举:enum MyEnum { FOO, BAR, BAZ, ; }

      使用现代 IDE 时,如果名称不是有效的枚举常量,您可以直接在注释元素(或注释的值)上显示错误。你甚至可以提供自动补全提示,所以当用户写@MyAnnotation(clazz = MyEnum.class, name = "B")并在写完B后点击自动补全的热键时,你可以为他提供一个可供选择的列表,其中包含所有开始的常量B:BAR 和 BAZ。

      我对实现默认值的建议是创建一个标记注释来将枚举常量声明为该枚举的 默认 值。用户仍然需要提供枚举类型,但可以省略名称。不过,可能还有其他方法可以将值设为默认值。

      这是一个关于 apt 的 tutorial,这里是 AbstractProcessor,它应该被扩展以覆盖 getCompletions 方法。

      【讨论】:

      • 谢谢。我知道注释处理器。我看到了沙丘的解决方案。我只是想知道是否有人有更好的主意。这就是我设置赏金的原因。
      • 用处理器检查一次注释比总是在运行时检查它们要好。您建议的泛型会很好,但不幸的是不可用。枚举名称似乎是解决问题的唯一方法(除非您使用其他东西代替枚举),但它们不是编译时间常数,因此必须编写为编译时间常数字符串。在编译时验证这些字符串对用户来说非常方便。
      【解决方案5】:

      如果构造函数参数中未提供该值,但不关心运行时的泛型类型,则不完全确定您所说的获取默认值是什么意思。

      以下内容有效,但有点丑陋。

      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      public class Main {
      
          @MyAnnotation(clazz = MyEnum.class, name = "A")
          private MyEnum value;
      
          public static v oid main(String[] args) {
              new Main().printValue();
          }
      
          public void printValue() {
              System.out.println(getValue());
          }
      
          public MyEnum getValue() {
              if (value == null) {
                  value = getDefaultValue("value", MyEnum.class);
              }
              return value;
          }
      
          private <T extends Enum<?>> T getDefaultValue(String name, Class<T> clazz) {
      
              try {
                  MyAnnotation annotation = Main.class.getDeclaredField(name)
                          .getAnnotation(MyAnnotation.class);
      
                  Method valueOf = clazz.getMethod("valueOf", String.class);
      
                  return clazz.cast(valueOf.invoke(this, annotation.value()));
      
              } catch (SecurityException e) {
                  throw new IllegalStateException(e);
              } catch (NoSuchFieldException e) {
                  throw new IllegalArgumentException(name, e);
              } catch (IllegalAccessException e) {
                  throw new IllegalStateException(e);
              } catch (NoSuchMethodException e) {
                      throw new IllegalStateException(e);
              } catch (InvocationTargetException e) {
                  if (e.getCause() instanceof RuntimeException) {
                      throw (RuntimeException) e.getCause();
                      /* rethrow original runtime exception 
                       * For instance, if value = "C" */
                  }
                  throw new IllegalStateException(e);
              }
          }
      
          public enum MyEnum {
              A, B;
          }
      
          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.FIELD)
          public @interface MyAnnotation {
      
              Class<? extends Enum<?>> clazz();
      
              String name();
          }
      }
      

      编辑:我将 getDefaultValue 更改为通过枚举的 valueOf 方法工作,因此如果给定的值不是枚举的引用实例,则会给出更好的错误消息。

      【讨论】:

      • "如果构造函数 args 中未提供所述值,则不完全确定您所说的获取默认值是什么意思" -> 应在注释“实例”中定义默认值带注释的项目。
      【解决方案6】:

      您的泛型类型语法有点偏离。应该是:

      public @interface MyAnnotation<T extends Enum<T>> {...
      

      但编译器报错:

      语法错误,注解声明不能有类型参数

      好主意。好像不支持。

      【讨论】:

        【解决方案7】:

        简单地说,你不能那样做。枚举不能轻易用作泛型类型;也许有一个例外,即 Enums 实际上可以实现允许某种动态使用的接口。但这不适用于注释,因为可以使用的类型集受到严格限制。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-23
          • 2015-08-22
          • 2011-10-14
          • 1970-01-01
          • 2016-10-10
          • 2020-03-03
          相关资源
          最近更新 更多