Java允许enum
作为注释值的值。如何为enum
注释值定义一种通用的默认enum
值?
我考虑过以下内容,但不会编译:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public <T extends Enum<T>> @interface MyAnnotation<T> {
T defaultValue();
}
是否有解决此问题的方法?
BOUNTY
似乎没有直接解决这个Java角落案例。所以,我正在开始寻找最优雅的解决方案来解决这个问题。
理想解决方案理想符合以下条件:
最佳解决方案
By Dunes:
@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
。
这需要为每个枚举类型创建一个注释类型,但保证编译时检查类型安全。
答案 0 :(得分:5)
当你说如果在构造函数args中没有提供所述值,而不是在运行时关心泛型类型时,不完全确定你的意思是什么意思。
以下作品,但有点丑陋。
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();
}
}
编辑:我通过枚举的valueOf方法更改了getDefaultValue,因此如果给定的值不是枚举的引用实例,则会给出更好的错误消息。
答案 1 :(得分:3)
简单地说,你不能这样做。枚举不能轻易用作泛型类型;也许有一个例外,即Enums实际上可以实现允许某种动态使用的接口。但这对注释无效,因为可以使用的一组类型受到严格限制。
答案 2 :(得分:3)
您的泛型类型语法有点偏差。它应该是:
public @interface MyAnnotation<T extends Enum<T>> {...
但编译器给出了错误:
语法错误,注释声明不能有类型参数
好主意。看起来不受支持。
答案 3 :(得分:3)
使用注释的框架确实可以从使用apt中获益。它是 javac 中包含的预处理程序,它可以让您分析声明及其注释(但不包括方法中的局部声明)。
对于您的问题,您需要编写一个AnnotationProcessor
(一个用作预处理起点的类)来使用Mirror API来分析注释。实际上,Dunes的注释非常接近这里需要的东西。太糟糕的枚举名称不是常量表达式,否则Dunes的解决方案会非常好。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
Class<? extends Enum<?>> clazz();
String name() default "";
}
以下是enum的示例:enum MyEnum { FOO, BAR, BAZ, ; }
使用现代IDE时,如果名称不是有效的枚举常量,则可以直接在注释元素(或注释的值)上显示错误。您甚至可以提供自动完成提示,因此当用户在编写 B 后编写@MyAnnotation(clazz = MyEnum.class, name = "B")
并点击热键进行自动完成时,您可以为他提供一个可供选择的列表,其中包含所有以 B 开头的常量:BAR和BAZ。
我建议实现默认值是创建一个标记注释,以将枚举常量声明为该枚举的默认值。用户仍然需要提供枚举类型,但可以省略名称。可能还有其他方法,将值设为默认值。
以下是关于apt的tutorial,此处AbstractProcessor应扩展为覆盖getCompletions
方法。
答案 4 :(得分:2)
我的建议与kapep's建议类似。不同之处在于我建议使用注释处理器进行代码创建。
一个简单的例子是,如果您打算仅将此用于您自己编写的枚举。使用特殊枚举注释枚举。然后,注释处理器将为该枚举生成新的注释。
如果您使用许多未编写的枚举,那么您可以实现一些名称映射方案:枚举名称 - &gt;注释名称。然后,当注释处理器在您的代码中遇到其中一个枚举时,它会自动生成相应的注释。
你要求:
答案 5 :(得分: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;
当clazz
为ImplicitType.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
进行注释。但是,您必须通过注释对象上的反射来访问该值。似乎这种方法在框架方面更复杂。
答案 6 :(得分: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<T>
,而不是默认为在班级宣布的第一个枚举常量。
当然,这将是一个类范围的默认声明,但这符合我的需要。 YMMV:)