java注解中的直接呈现、间接呈现、呈现、关联如何理解?

How to understand directly present, indirectly present, present, and associated in java annotation?

AnnotatedElement 的 java 文档中,我读到了术语:直接存在、间接存在、存在和关联,但我不能理解他们的意思。

例如,在文档中它说:

An annotation A is directly present on an element E if E has a RuntimeVisibleAnnotations or RuntimeVisibleParameterAnnotations or RuntimeVisibleTypeAnnotations attribute, and the attribute contains A.

但我不知道 RuntimeVisibleAnnotations 属性 是什么以及“该属性包含 A”是什么意思。

谁能举一些例子来说明他们的区别,谢谢!

属性(例如RuntimeVisibleAnnotations

提到的属性是 class 文件格式的一部分。例如,RuntimeVisibleAnnotaitons 属性描述为 §4.7.16 of the Java Virtual Machine Specification:

The RuntimeVisibleAnnotations attribute is a variable-length attribute in the attributes table of a ClassFile, field_info, or method_info structure (§4.1,§4.5, §4.6). The RuntimeVisibleAnnotations attribute records run-time visible annotations on the declaration of the corresponding class, field, or method.

There may be at most one RuntimeVisibleAnnotations attribute in the attributes table of a ClassFile, field_info, or method_info structure.

[...]

您可以通过 javap 检查字节码来查看此属性。例如,这个:

@FunctionalInterface // has runtime retention
public interface Foo {
  void bar(); // satisfy functional interface requirements
}

给出这个:

public interface Foo
  minor version: 0
  major version: 58
  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
  this_class: #1                          // Foo
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 1, attributes: 2
Constant pool:
   #1 = Class              #2             // Foo
   #2 = Utf8               Foo
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               bar
   #6 = Utf8               ()V
   #7 = Utf8               SourceFile
   #8 = Utf8               Foo.java
   #9 = Utf8               RuntimeVisibleAnnotations
  #10 = Utf8               Ljava/lang/FunctionalInterface;
{
  public abstract void bar();
    descriptor: ()V
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Foo.java"
RuntimeVisibleAnnotations:
  0: #10()
    java.lang.FunctionalInterface

您可以在底部看到 class(接口)FooRuntimeVisibleAnnotations 属性。该属性包含一个条目:java.lang.FunctionalInterface。这意味着所述注释直接出现在 Foo.


存在的种类

假设我们有以下注释(省略导入):

@Retention(RUNTIME)
@Inherited
public @interface Foo {}
@Retention(RUNTIME)
@Inherited
@Repeatable(BarList.class)
public @interface Bar {}
@Retention(RUNTIME)
@Inherited
public @interface BarList {
  Bar[] value();
}

那么如果我们有:

@Foo
@BarList({@Bar, @Bar})
public class Parent {}
public class Child extends Parent {}

以下为真:

  1. Foo 直接出现在 Parent
  2. Foo 存在 Child(因为它是继承的)
  3. BarList 直接出现在 Parent
  4. BarList 存在 Child(因为它是继承的)
  5. 两个 Bar 注释 间接存在 Parent 上(因为它们是可重复的并且在它们的容器注释中)
  6. 两个 Bar 注释都与 Child 相关(因为它们是继承的、可重复的,并且在它们的容器注释中)

一些补充说明:

  • 如果注释直接存在E上,那么注释也存在并且关联E

  • 如果注释 间接存在 E 上,则注释也 关联 E

  • 一个注解被继承当且仅当:

    1. 注释类型是meta-annotated和java.lang.annotation.Inherited
    2. 注解存在于祖先class(注解继承仅适用于classes,不适用于接口、方法、字段等)
  • 当注释被继承但出现在整个 class 层次结构中的多个 class 上时,则只会找到“最新”注释(即最接近的注释查询层次结构的底部)

  • 如果注解在classE存在,但注解不可继承,则注解不会被继承存在 E

    的子class相关联
  • 可重复的注解不需要显式地放在它们对应的容器注解中。例如,上面可以使用:

    @Foo
    @Bar
    @Bar
    public class Parent {}
    

    这两个 Bar 注释被编译器隐式包装在它们的容器注释中(即 BarList)。这意味着两个 Bar 注释仍然 间接存在 Parent 并且 Child 相关联 。但是,只有 多于一个 的可重复注释才会发生这种隐式包装。因此,如果只有一个 Bar 注释,那么它将 directly present on Parentpresent on Child.