枚举Java枚举值的枚举值

时间:2011-08-15 21:58:33

标签: java enums annotations default-value

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

我考虑过以下内容,但不会编译:

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

    T defaultValue();

}

是否有解决此问题的方法?

BOUNTY

似乎没有直接解决这个Java角落案例。所以,我正在开始寻找最优雅的解决方案来解决这个问题。

理想解决方案理想符合以下条件:

  1. 可在所有枚举中重复使用的一个注释
  2. 从注释实例中检索默认枚举值作为枚举的最小努力/复杂性
  3. 最佳解决方案

    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

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

7 个答案:

答案 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;注释名称。然后,当注释处理器在您的代码中遇到其中一个枚举时,它会自动生成相应的注释。

你要求:

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

答案 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;

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进行注释。但是,您必须通过注释对象上的反射来访问该值。似乎这种方法在框架方面更复杂。

答案 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:)