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

发布于 2024-11-29 20:58:32 字数 1695 浏览 1 评论 0原文

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

我考虑了以下问题,但无法编译:

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

    T defaultValue();

}

这个问题是否有解决方案?

BOUNTY

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

理想的解决方案应该理想满足以下标准:

  1. 一个注释可在所有枚举上重用
  2. 从注释实例中检索作为枚举的默认枚举值的工作量/复杂性最小

最佳到目前为止的解决方案

作者: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 来获取提供的默认值价值。

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

Java allows enum as values for annotation values. How can I define a kind of generic default enum value for an enum annotation value?

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.

The ideal solution should ideally meet 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

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().

IMPROVEMENT

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

@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 MyEnumAnnotation on fields for the provided default value.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

征棹 2024-12-06 20:58:32

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

下面的方法可行,但有点丑陋。

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 方法工作,因此如果给定的值不是枚举的引用实例,则会给出更好的错误消息。

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.

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.

握住你手 2024-12-06 20:58:32

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

答案 1:

如果您只是想编写尽可能少的代码,这是我的建议,扩展Dunes'< /strong>回答:

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

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

Answer 1:

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

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 clazz is ImplicitType.class, use the fields type as enum class.

Answer 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.

看春风乍起 2024-12-06 20:58:32

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

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.

半岛未凉 2024-12-06 20:58:32

您的泛型类型语法有点不对劲。应该是:

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

但是编译器给出错误:

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

好主意。看起来不支持。

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.

怪我入戏太深 2024-12-06 20:58:32

使用注释的框架确实可以从使用 apt 中受益。它是 javac 中包含的预处理器,它可以让您分析声明及其注释(但不能分析方法内的本地声明)。

对于您的问题,您需要编写一个 AnnotationProcessor (用作预处理起点的类)来使用 镜像 API。实际上,Dunes 的注释非常接近这里所需要的。可惜枚举名称不是常量表达式,否则 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< 后按下热键进行自动完成< /em>,你可以给他提供一个列表供他选择,其中包含所有以B开头的常量:BAR和BAZ。

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

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

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).

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.

@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, ; }

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.

My suggestion to implement the default value is to create a marker annotation to declare an enum constant as default value 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 tutorial about apt and here the AbstractProcessor which should be extended to override the getCompletions method.

各自安好 2024-12-06 20:58:32

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

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

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

您要求:

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

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

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
微凉 2024-12-06 20:58:32

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

实际的@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而不是默认为类中声明的第一个 Enum 常量。

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

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

The actual @Default interface:

@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.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文