Java 枚举注释值的枚举默认值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7071301/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 18:32:14  来源:igfitidea点击:

Enum default value for Java enum annotation value

javaenumsannotationsdefault-value

提问by Jér?me Verstrynge

Java allows enumas values for annotation values. How can I define a kind of generic default enumvalue for an enumannotation value?

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

I have considered the following, but it won't compile:

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

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

    T defaultValue();

}

Is there a solution to this issue or not?

这个问题有没有解决方案?

BOUNTY

赏金

Is does not seem like there is a direct solution to this Java corner case. So, I am starting a bounty to find the most elegant solution to this issue.

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

The idealsolution should ideallymeet the following criteria:

理想的解决方案应该非常符合下列条件:

  1. One annotation reusable on all enums
  2. Minimum effort/complexity to retrieve the default enum value as an enum from annotation instances
  1. 一个可在所有枚举上重用的注释
  2. 从注释实例中检索默认枚举值作为枚举的最小工作量/复杂性

BEST SOLUTION SO FAR

迄今为止最好的解决方案

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;

Of course, we can't force the user to specify a valid default value at compile time. However, any annotation pre-processing can verify this with valueOf().

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

IMPROVEMENT

改进

Arian provides an elegant solution to get rid of clazzin annotated fields:

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;

The annotation processor should search for both MyEnumAnnotationon fields for the provided default value.

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

This requires the creation of one annotation type per enum type, but guarantees compile time checked type safety.

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

采纳答案by Cephalopod

I'm not sure what your use case is, so I have two answers:

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

Answer 1:

答案 1:

If you just want to write as little code as possible, here is my suggestion extending Dunes'answer:

如果您只想编写尽可能少的代码,那么我的建议是扩展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;

When clazzis ImplicitType.class, use the fields type as enum class.

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

Answer 2:

答案 2:

If you want to do some framework magic and want to maintain compiler checked type safety, you can do something like this:

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

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

And in the client code, you would have

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

/** 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;

In this case you would scan the field for annotations which again are annotated with MyAnnotation. You will have to access the value via reflection on the annotation object, though. Seems like this approach is more complex on the framework side.

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

回答by Dunes

Not entirely sure what you mean when you say get a default value if said value wasn't provided in the constructor args, but not be caring about the generic type at runtime.

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

The following works, but is a bit of an ugly hack though.

以下工作,但有点丑陋的黑客虽然。

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();
    }
}

edit: I changed the getDefaultValue to work via the valueOf method of enums, thus giving a better error message if the value given is not reference instance of the enum.

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

回答by kapex

Frameworks using annotations can really profit from using apt. It's a preprocesor contained in javac, which will let you analyse declarations and their annotations (but not local declarations inside methods).

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

For your problem you would need to write an AnnotationProcessor(a class used as starting point for preprocessing) to analyse the annotation by using the Mirror API. Actually Dunes' annotation is pretty close to what is needed here. Too bad enum names aren't constant expressions, otherwise Dunes' solution would be pretty nice.

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

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

And here's an example enum: enum MyEnum { FOO, BAR, BAZ, ; }

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

When using a modern IDE, you can display errors on directly on the annotation element (or the annotation's value), if the name isn't a valid enum constant. You can even provide auto complete hints, so when a user writes @MyAnnotation(clazz = MyEnum.class, name = "B")and hits the hotkeys for auto-completion after writing B, you can provide him a list to choose from, containing all the constants starting with B: BAR and BAZ.

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

My suggestion to implement the default value is to create a marker annotation to declare an enum constant as defaultvalue for that enum. The user would still need to provide the enum type, but could omit the name. There are probably other ways though, to make a value the default one.

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

Here's a tutorialabout apt and here the AbstractProcessorwhich should be extended to override the getCompletionsmethod.

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

回答by StaxMan

Simply put, you can not do that. Enums can not easily be used as generic types; with perhaps one exception, which is that Enums can actually implement interfaces which allows somewhat dynamic usage. But that won't work with annotations as set of types that can be used is strictly limited.

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

回答by Bohemian

Your generic type syntax is a little off. It should be:

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

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

but compiler gives error:

但编译器给出错误:

Syntax error, annotation declaration cannot have type parameters

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

Nice idea. Looks like it's not supported.

好主意。好像不支持。

回答by emory

My suggestion is similar to kapep'ssuggestion. The difference is that I propose using the annotation processor for code creation.

我的建议类似于kapep 的建议。不同之处在于我建议使用注释处理器来创建代码。

A simple example would be if you intended to use this only for enums that you yourself wrote. Annotate the enum with a special enum. The annotation processor will then generate a new annotation just for that enum.

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

If you work with a lot of enums that you did not write, then you could implement some name mapping scheme: enum name -> annotation name. Then when the annotation processor encountered one of these enums in your code, it would generate the appropriate annotation automatically.

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

You asked for:

您要求:

  1. One annotation reusable on all enums ... technically no, but I think the effect is the same.
  2. Minimum effort/complexity to retrieve the default enum value as an enum from annotation instances ... you can retrieve the default enum value without any special processing
  1. 一个注释可在所有枚举上重复使用......技术上没有,但我认为效果是一样的。
  2. 从注释实例中检索默认枚举值作为枚举的最小努力/复杂性......您可以在没有任何特殊处理的情况下检索默认枚举值

回答by Michael Ressler

I had a similar need and came up with the following pretty straightforward solution:

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

The actual @Defaultinterface:

实际@Default界面:

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

Usage:

用法:

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

Finding the default:

查找默认值:

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]);
    }   
}

I've also played around with returning an Optional<T>instead of defaulting to the first Enum constant declared in the class.

我还Optional<T>尝试过返回一个而不是默认为类中声明的第一个 Enum 常量。

This would, of course, be a class-wide default declaration, but that matches what I need. YMMV :)

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