注释中的泛型类型

2022-01-13 00:00:00 annotations java generics

考虑以下代码:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class AnnotationTest {

    @GenericAnnotation<String>(foo = "Test")
    public class Bar1 {

    }

    @ObjectAnnotation(foo = "Test")
    public class Bar2 {

    }

    @WorkingAnnotation(foo = "Test")
    public class Bar3 {

    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface GenericAnnotation<T> {

        public T foo();

    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface ObjectAnnotation {

        public Object foo();

    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface WorkingAnnotation {

        public String foo();

    }

}

Bar1 根本不会编译.一大堆错误.

Bar1 won't compile at all. Huge mess of errors.

Bar2 可以正常编译,但 ObjectAnnotation 注释不会.

Bar2 will compile fine, but the ObjectAnnotation annotation won't.

Bar3 可以正常编译,但不允许泛型类型.

Bar3 will compile fine, but doesn't allow generic types.

如果 - 例如 - 我试图设置一个默认值以防某个字段无法加载.此类可能是 IntegerStringBoolean[],实际上是任何可能的类型.这意味着要处理所有可能的情况需要大量注释.

If - for example - I am trying to set a default value in case a certain field can't be loaded. This class might be an Integer, String, Boolean[], really any of the possible types. This means a whole mess of annotations for handling every possibly case.

是否有适当的方法来处理注解中的泛型类型?如果没有,是否有明确的原因?

Is there a proper way to handle generic types in an annotation? If not, is there a clear reason why?

推荐答案

编译器的错误消息令人困惑,纯粹是因为它没有被安排来接受这样的语法.

The compiler's error messages are confusing purely because it is not laid out to accept such a syntax.

类似的问题已经发布这里.

JLS,第 9.6 节 说明注释声明的一般语法如下:

The JLS, Section 9.6 states the general syntax of an annotation declaration as follows:

AnnotationType声明:

AnnotationTypeDeclaration:

{InterfaceModifier} @ interface Identifier AnnotationTypeBody

令牌标识符,在 JLS,描述类型的名称;无论是类、枚举、接口还是注解.

The token Identifier, in the terms of the JLS, describes the name of the type; be it class, enum, interface or annotation.

不能使用泛型类型声明注解,因为它们由标记 TypeParameters,这里不包含.

An annotation can not be declared with generic types, as these are referred to by the token TypeParameters, which is not included here.

至于为什么,这导致

查看下一项,Section 9.6.1,我们发现注解可以采用的类型限制:

Looking into the next item, Section 9.6.1, we spot the restriction of types an annotation can take:

注解类型中声明的方法的返回类型必须是以下之一,否则会发生编译时错误:

The return type of a method declared in an annotation type must be one of the following, or a compile-time error occurs:

  • 原始类型
  • 字符串
  • 类或类的调用(§4.5)
  • 枚举类型
  • 注释类型
  • 其组件类型是上述类型之一的数组类型(§10.1).

(一些特殊情况在下面进一步讨论,但与这个问题无关)

(some special cases are addressed further below, but are irrelevant to this problem)

这些限制是导致您的第二个注释沮丧的原因.它根本不是为了容纳所有类型的对象而设计的.它甚至不能容纳原始类型的盒装类型!

These restrictions are what's causing the dejection of your second annotation. It is simply not designed to hold all types of Objects. It cannot even hold the boxed type of a primitive!

现在,回到案例 1:为什么泛型注解在这个语法中是错误的?甚至在任何类型擦除发生之前,您的 GenericAnnotation 基本上就可以成为每种类型 Object 的持有者.在其 foo 属性的定义中,它与您的 ObjectAnnotation 完全相同.

Now, back to Case 1 for a bit: Why are generic Annotations a wrong thing in this grammar? Even before any type erasure occurs, your GenericAnnotation can basically be a holder for every type of Object. It is, in the definition of its foo property, exactly the same as your ObjectAnnotation.

现在的问题是,为什么会有这种限制?通过限制注释可能包含的值,您可以获得两个优点:首先,所有值都是编译时常量.如果不大量使用反射,就无法将依赖于运行时的值传入或传出注释.

Now, the question is, why is that limitation in place? By limiting the values an annotation might contain, you get two advantages: First, all the values are to be compile-time constants. There is no way to get a runtime-dependent value either into or out of an annotation without heavy use of reflection.

这立即带来了第二个优势:没有人会尝试将不纯(副作用)代码放入可能在应用程序生命周期中的任何随机点加载的注释中.如果您可以引入任何类型的对象,它们的构造函数可能会在可能无法检测到的时间产生任何类型的副作用,如果使用这种技术,则会增加调试的复杂性.

This immediately brings one to the second advantage: Nobody will even get the temptation to put impure (side-effecting) code inside annotations which may be loaded at any random point in the applications lifetime. If you could introduce any type of object, their constructors could do any kind of side-effect at a possibly undetectable time, increasing the complexity of debugging if this technique were used.

或者至少,这对我来说是最合乎逻辑的,因为我找不到 Sun 或其继任者的官方消息.

Or at least, that's what seems the most logical to me, as there is no official word on this from either Sun or their successors that I could find.

遗憾的是,没有简单、富有表现力和简单的解决方法.由于您无法将进程放入@Annotation-uses,因此您甚至无法使用 ObjectOutputStream 和 ByteArrayOutputStream 将对象序列化为字节数组.

There is, sadly, no simple, expressive and easy workaround to this. As you cannot place processes into @Annotation-uses, you can not even do something fancy as serializing the object into a byte array using ObjectOutputStream and ByteArrayOutputStream.

相关文章