Java 带有类型参数的注释属性

Java 带有类型参数的注释属性,java,generics,annotations,Java,Generics,Annotations,定义Java接口时,可以使用类型参数声明方法,例如: public interface ExampleInterface { <E extends Enum<E>> Class<E> options(); } 无法使用类型参数声明注释属性的确切原因是什么?Java™ 语言规范第三版说: 由于注释类型声明的 上下文无关语法: Annotation type declarations cannot be generic. No extends claus

定义Java接口时,可以使用类型参数声明方法,例如:

public interface ExampleInterface {
    <E extends Enum<E>> Class<E> options();
}

无法使用类型参数声明注释属性的确切原因是什么?

Java™ 语言规范第三版说:

由于注释类型声明的 上下文无关语法:

Annotation type declarations cannot be generic.
No extends clause is permitted. (Annotation types implicitly extend annotation.Annotation.)
Methods cannot have any parameters
Methods cannot have any type parameters
  • 注释类型声明不能是泛型的
  • 不允许使用扩展条款。(注释类型隐式扩展了Annotation.Annotation。)
  • 方法不能有任何参数
  • 方法不能具有任何类型参数
  • 方法声明不能有throws子句

    • 他们想引入注释,以便人们只将它们用作、、以及注释。并防止开发人员在其中加入逻辑。 i、 e.使用注释开始编程,在我看来,这可能会使Java看起来像一种完全不同的语言。 因此,Java语言规范中有上下文无关的语法注释

      对注释类型声明施加以下限制 由于其上下文无关语法

      Annotation type declarations cannot be generic.
      No extends clause is permitted. (Annotation types implicitly extend annotation.Annotation.)
      Methods cannot have any parameters
      Methods cannot have any type parameters
      
      ()

      为了更好地理解我的意思,看看这个JVM黑客做了什么:


      他创建和或注释作为说明,并使用它们处理其他注释。无价之宝

      我认为这是可能的,但它需要在语言规范中添加大量内容,这是不合理的

      首先,对于枚举示例,可以使用
      Class>options

      Class>options
      中还有另一个问题,因为
      Enum
      不是
      Enum
      的子类型,这在混乱的原始类型处理中是一个相当偶然的事实

      回到一般问题上来。由于在有限的属性类型中,
      Class
      是唯一一个具有类型参数的类型,并且通配符通常具有足够的表达能力,因此您的问题不太值得解决

      让我们进一步概括这个问题,假设有更多的属性类型,并且通配符在许多情况下不够强大。例如,假设允许使用
      Map
      ,例如

      Map<String,Integer> options();
      
      options={"a":1, "b":2} // suppose we have "map literal"
      
      在当前对方法类型参数、推理规则、继承规则等的理解中,这根本不起作用。我们需要更改/添加很多东西才能使其起作用

      在这两种方法中,如何将
      X
      交付给注释处理器都是一个问题。我们必须发明一些额外的机制来携带带有实例的类型参数。

      的Java语言规范描述了注释。其中一句话是:

      如果注释类型中声明的方法的返回类型不是以下类型之一,则为编译时错误:基元类型之一、字符串、类和类的任何调用、枚举类型(§8.9)、注释类型或前述类型之一的数组(§10)。如果在注释类型中声明的任何方法的签名重写等效于在类对象或接口annotation.annotation中声明的任何公共或受保护方法的签名,则这也是编译时错误

      然后它说了以下几点,我认为这是解决这个问题的关键:

      请注意,这与禁止泛型方法并不冲突,因为通配符消除了显式类型参数的需要

      所以它建议我应该使用通配符,并且类型参数不是必需的。为了摆脱原始类型
      Enum
      ,我只需使用
      Enum
      ,正如他在回答中建议的那样:

      public @interface ExampleAnnotation {
          Class<? extends Enum<?>> options();
      }
      
      public@接口示例注释{
      类>选项();
      }
      

      可能允许类型参数会打开一个蠕虫罐头,因此语言设计者决定干脆不允许它们,因为你可以通过通配符得到你需要的东西。

      我承认我在这里参加聚会迟到了,但我自己也为这个确切的问题挣扎了很长一段时间,我想增加一个稍微不同的看法

      注意:这是一个相当长的答案,除非您对JVM的底层细节感兴趣,或者您正在JVM之上实现新的编程语言,否则您可能不需要阅读它

      首先,Java语言和作为底层平台的Java虚拟机之间存在差异。Java语言由Java语言规范管理,一些人已经在他们的答案中引用了Java语言规范。JVM由Java虚拟机规范管理,除了Java之外,它还支持其他几种编程语言,如Scala、Ceylon、Xtend和Kotlin。JVM是所有这些语言的共同特征,因此,它必须比基于它的语言具有更大的权限

      现有答案中引用的限制是Java语言的限制,而不是JVM的限制。在大多数情况下,这些限制在JVM级别并不存在

      例如,假设您想定义如下内容(最后解释为什么要这样做):

      (上面的代码是用语法编写的,但可以很容易地转换为常规Java)

      简而言之,此代码将创建一个参数化注释(
      @class
      ),并将其用作另一个注释(
      @before
      )的属性,其中类型参数绑定到
      ?扩展可运行的
      。通过将
      forEach[load(…)]
      替换为
      forEach[saveIn(…)]
      (生成实际的类文件)并在同一文件夹中编译一个小型Java测试程序,可以轻松验证生成的代码的有效性:

      import java.lang.reflect.Method;
      import java.lang.annotation.Annotation;
      
      public class TestAnnotation
      {
        @before
        public static void main(String[] arg) throws Exception
        {
          Method main = TestAnnotation.class.getDeclaredMethod("main", String[].class);
          @SuppressWarnings("unchecked")
          Class<? extends Annotation> beforeAnnotation = (Class<? extends Annotation>)Class.forName("before");
          Annotation before = main.getAnnotation(beforeAnnotation);
          Method code = before.getClass().getDeclaredMethod("code");
          Object classAnnotation = code.invoke(before);
          System.err.println(classAnnotation);
        }
      }
      
      为了更好地理解这能实现什么(以及不能实现什么)
      public @interface ExampleAnnotation {
          Class<? extends Enum<?>> options();
      }
      
      @Retention(RUNTIME)
      public @interface before
      {
        class<? extends Runnable> code() default @class(Initializer.class);
      }
      
      public @interface class<T>
      {
        Class<T> value();
      }
      
      public class Initializer extends Runnable
      {
        @Override
        public void run()
        {
          // initialization code
        }
      }
      
      import java.lang.annotation.Annotation
      import java.lang.annotation.Retention
      import java.lang.annotation.RetentionPolicy
      import net.bytebuddy.ByteBuddy
      import net.bytebuddy.description.annotation.AnnotationDescription
      import net.bytebuddy.description.annotation.AnnotationValue
      import net.bytebuddy.description.modifier.Visibility
      import net.bytebuddy.description.type.TypeDefinition
      import net.bytebuddy.description.type.TypeDescription
      import net.bytebuddy.description.type.TypeDescription.Generic
      import net.bytebuddy.dynamic.DynamicType.Unloaded
      import net.bytebuddy.dynamic.scaffold.TypeValidation
      import net.bytebuddy.implementation.StubMethod
      import static java.lang.annotation.RetentionPolicy.RUNTIME
      import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType
      import static net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType.Latent.boundedAbove
      import static net.bytebuddy.description.type.TypeDescription.CLASS
      import static net.bytebuddy.matcher.ElementMatchers.named
      
      class AnnotationWithTypeParameter
      {
          def void createAnnotationWithTypeParameter()
          {
              val ByteBuddy codeGenerator = new ByteBuddy().with(TypeValidation.DISABLED)
              val TypeDefinition T = TypeDescription.Generic.Builder.typeVariable("T").build
              val TypeDefinition classT = TypeDescription.Generic.Builder.parameterizedType(CLASS, T).build
              val Unloaded<? extends Annotation> unloadedAnnotation = codeGenerator
                  .makeAnnotation
                  .merge(Visibility.PUBLIC)
                  .name("class")
                  .typeVariable("T")
                  .defineMethod("value", classT, Visibility.PUBLIC)
                  .withoutCode
                  .make
              val TypeDescription classAnnotation = unloadedAnnotation.typeDescription
              val Unloaded<Runnable> unloadedRunnable = codeGenerator
                  .subclass(Runnable).merge(Visibility.PUBLIC).name("Initializer")
                  .method(named("run")).intercept(StubMethod.INSTANCE)
                  .make
              val TypeDescription typeInitializer = unloadedRunnable.typeDescription
              val AnnotationDescription.Builder a = AnnotationDescription.Builder.ofType(classAnnotation)
                  .define("value", typeInitializer)
              val AnnotationValue<?, ?> annotationValue = new AnnotationValue.ForAnnotationDescription(a.build)
              val TypeDescription classRunnable = new TypeDescription.ForLoadedType(Runnable)
              val Generic.Builder classExtendsRunnable = parameterizedType(classAnnotation, boundedAbove(classRunnable.asGenericType, classRunnable.asGenericType))
              val Retention runtimeRetention = new Retention()
              {
                  override Class<Retention> annotationType() {Retention}
                  override RetentionPolicy value() {RUNTIME}
              }
              val Unloaded<? extends Annotation> unloadedBefore = codeGenerator
                  .makeAnnotation
                  .merge(Visibility.PUBLIC)
                  .name("before")
                  .annotateType(runtimeRetention)
                  .defineMethod("code", classExtendsRunnable.build, Visibility.PUBLIC)
                  .defaultValue(annotationValue)
                  .make
              #[unloadedBefore, unloadedAnnotation, unloadedRunnable].forEach[load(class.classLoader).loaded]
              //                 ...or alternatively something like: .forEach[saveIn(new File("/tmp"))]
          }
      }
      
      import java.lang.reflect.Method;
      import java.lang.annotation.Annotation;
      
      public class TestAnnotation
      {
        @before
        public static void main(String[] arg) throws Exception
        {
          Method main = TestAnnotation.class.getDeclaredMethod("main", String[].class);
          @SuppressWarnings("unchecked")
          Class<? extends Annotation> beforeAnnotation = (Class<? extends Annotation>)Class.forName("before");
          Annotation before = main.getAnnotation(beforeAnnotation);
          Method code = before.getClass().getDeclaredMethod("code");
          Object classAnnotation = code.invoke(before);
          System.err.println(classAnnotation);
        }
      }
      
      @class(value=class Initializer)
      
      Classfile /private/tmp/class.class
        Last modified Feb 28, 2020; size 265 bytes
        MD5 checksum f57e09ce9d174a6943f7b09704cbdea3
      public interface class<T extends java.lang.Object> extends java.lang.annotation.Annotation
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
      Constant pool:
         #1 = Utf8               class
         #2 = Class              #1             // class
         #3 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/annotation/Annotation;
         #4 = Utf8               java/lang/Object
         #5 = Class              #4             // java/lang/Object
         #6 = Utf8               java/lang/annotation/Annotation
         #7 = Class              #6             // java/lang/annotation/Annotation
         #8 = Utf8               value
         #9 = Utf8               ()Ljava/lang/Class;
        #10 = Utf8               ()Ljava/lang/Class<TT;>;
        #11 = Utf8               Signature
      {
        public abstract java.lang.Class<T> value();
          descriptor: ()Ljava/lang/Class;
          flags: ACC_PUBLIC, ACC_ABSTRACT
          Signature: #10                          // ()Ljava/lang/Class<TT;>;
      }
      Signature: #3                           // <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/annotation/Annotation;
      
      Classfile /private/tmp/before.class
        Last modified Feb 28, 2020; size 382 bytes
        MD5 checksum d2166167cf2adb8989a77dd320f9f44b
      public interface before extends java.lang.annotation.Annotation
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
      Constant pool:
         #1 = Utf8               before
         #2 = Class              #1             // before
         #3 = Utf8               java/lang/Object
         #4 = Class              #3             // java/lang/Object
         #5 = Utf8               java/lang/annotation/Annotation
         #6 = Class              #5             // java/lang/annotation/Annotation
         #7 = Utf8               Ljava/lang/annotation/Retention;
         #8 = Utf8               value
         #9 = Utf8               Ljava/lang/annotation/RetentionPolicy;
        #10 = Utf8               RUNTIME
        #11 = Utf8               code
        #12 = Utf8               ()Lclass;
        #13 = Utf8               ()Lclass<+Ljava/lang/Runnable;>;
        #14 = Utf8               Lclass;
        #15 = Utf8               LInitializer;
        #16 = Utf8               Signature
        #17 = Utf8               AnnotationDefault
        #18 = Utf8               RuntimeVisibleAnnotations
      {
        public abstract class<? extends java.lang.Runnable> code();
          descriptor: ()Lclass;
          flags: ACC_PUBLIC, ACC_ABSTRACT
          Signature: #13                          // ()Lclass<+Ljava/lang/Runnable;>;
          AnnotationDefault:
            default_value: @#14(#8=c#15)}
      RuntimeVisibleAnnotations:
        0: #7(#8=e#9.#10)