2011-08-15 201 views
18

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

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

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

    T defaultValue(); 

} 

是否有这个问题的解决与否?

BOUNTY

是似乎并不像有一个直接的解决方案,这个Java角落的情况。所以,我开始寻找解决这个问题的最优雅解决方案。

理想解决方案应该理想满足以下条件:在所有枚举

  1. 一个注解重复使用
  2. 最小的努力/复杂检索默认枚举值从注释实例枚举

SO OF FAR

沙丘:

@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()进行验证。

改进

阿里安提供了一个完美的解决方案,以在注释字段摆脱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上提供的默认值的字段。

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

+0

难道这不是毫无意义吗?每当你在运行时处理注解时,通用信息就会丢失。 – Dunes

+0

这将避免我必须为每个枚举类型定义一个注释,以便在代码中使用(这是编译时问题)。 – JVerstry

+0

我不是故意没有用。但是泛型只在编译时才知道。您已将注释标记为保留在运行时中。但要访问注释,你必须通过思考 - 你不会有任何想法是什么泛型类型。 – Dunes

回答

2

我不知道你的使用情况是什么检索默认枚举值,所以我有两个答案:

答1:

如果你只是想编写尽可能少的代码越好,这里是我的建议延长沙丘答案:

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; 

在这种情况下,你会扫描领域再次被注解为的注解。不过,您必须通过注释对象上的反射来访问该值。似乎这种方法在框架方面更为复杂。

+0

看起来很有趣,但是不应该在您的MyAnnotation版本中保留clazz,并且不应该使用@MyAnnotation(clazz = MyEnumType.class)对MyEnumAnnotation进行注释?或者我错过了什么? – JVerstry

+0

我希望现在更清楚。 – Cephalopod

+0

对于第二种方法,您不需要clazz字段,因为您可以直接从客户端注释中获取枚举值。如果您还需要值类型,则可以使用value()函数的返回类型。 – Cephalopod

3

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

3

您的通用类型语法有点关闭。它应该是:

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

但是编译器会发出错误:

Syntax error, annotation declaration cannot have type parameters

好主意。看起来不受支持。

+0

是的,我也试过。但... – JVerstry

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方法一样工作,从而得到更好的错误消息,如果给定的值不引用枚举的实例。

+0

Oooh,在这里探索的好主意...... – JVerstry

+0

“如果在构造函数args中未提供所述值时获取默认值,则不完全确定您的意思” - >应该定义默认值在注释项目上的注释“实例”中。 – JVerstry

3

使用注释的框架可以真正从使用apt中获益。这是一个预处理程序,包含在javac中,它可让您分析声明及其注释(但不包括方法内的本地声明)。

对于您的问题,您需要编写AnnotationProcessor(用作预处理起点的类)以使用Mirror API分析注释。实际上沙丘的注释非常接近这里所需要的。太糟糕的枚举名称不是常量表达式,否则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方法。

+0

谢谢。我知道关于注释处理器。我看到了Dune的解决方案。我只是想知道是否有人有更好的主意。这就是我设定赏金的原因。 – JVerstry

+1

使用处理器检查注释比总是在运行时检查注释更好。你建议的泛型会很好,但不幸的是不可用。枚举名称似乎是解决问题的唯一方法(除非使用其他名称而不是枚举),但它们不是编译时间常量,因此必须写为编译时常量字符串。在编译时验证这些字符串对用户来说非常方便。 – Kapep

2

我的建议类似于kapep's的建议。区别在于我建议使用注释处理器来创建代码。

一个简单的例子是,如果你打算只用于你自己写的枚举。用一个特殊的枚举对枚举进行注释。注释处理器然后将为该枚举生成新的注释。

如果你使用了很多没有编写的枚举,那么你可以实现一些名称映射方案:enum name - > annotation name。然后,当注释处理程序在您的代码中遇到这些枚举之一时,它会自动生成适当的注释。

您问:在所有枚举

  1. 一个注解重复使用......在技术上没有,但我认为效果是一样的。
  2. 最小的努力/复杂检索默认枚举值从注释实例枚举...你可以不需要任何特殊的处理
0

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

实际@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<T>,而不是默认在班级中声明的第一个枚举常量。

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