Java 枚举注释值的枚举缺省值

2022-09-02 10:21:55

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

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

@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

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


答案 1

不完全确定当你说如果构造函数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();
    }
}

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


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

当 is 时,将字段类型用作枚举类。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