JAVA注解的继承性

摘要

本文从三个方面介绍 java 注解的 **“继承性”**:

  1. 基于元注解 @Inherited,类上注解的继承性
  2. 基于类的继承,方法 / 属性上注解的继承性
  3. 基于接口的继承 / 实现,方法 / 属性上注解的继承性

一、基于 @Inherited

首先元注解@Inherited作为一个元注解,只能修饰其他注解类型(由@Target(ElementType.ANNOTATION_TYPE)决定)。

所谓的基于 @Inherited 的继承性,指的是 @Inherited 修饰的其他注解修饰类时,这个类的子类是否可以继承到父类的注解;主角是 @Inherited 修饰的其他注解,而不是 @Inherited 本身。

JDK 中 @Inherited 的说明文档很清楚的阐述了继承性:

当用户在一个程序元素类上,使用 AnnotatedElement 的相关注解查询方法,查询元注解 Inherited 修饰的其他注解类型 A 时,如果这个类本身并没有被注解 A 修饰,那么会自动查询这个类的父类是否被注解 A 修饰。查询过程会沿着类继承链一直向上查找,直到注解 A 被找到,或者到达继承链顶层(Object)。
如果元注解 Inherited 修饰的其他注解,修饰了除类之外的其他程序元素,那么继承性将会失效

下面的以 demo 说明:

public class ClassInheritedTest {
    @Target(value = ElementType.TYPE)
    @Retention(value = RetentionPolicy.RUNTIME)
    @Inherited // 声明注解具有继承性
    @interface AInherited {
        String value() default "";
    }
<span class="hljs-meta">@Target(value = ElementType.TYPE)</span>
<span class="hljs-meta">@Retention(value = RetentionPolicy.RUNTIME)</span>
<span class="hljs-meta">@Inherited</span> <span class="hljs-comment">// 声明注解具有继承性</span>
<span class="hljs-meta">@interface</span> BInherited {
    String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;
}

<span class="hljs-meta">@Target(value = ElementType.TYPE)</span>
<span class="hljs-meta">@Retention(value = RetentionPolicy.RUNTIME)</span>
<span class="hljs-comment">// 未声明注解具有继承性</span>
<span class="hljs-meta">@interface</span> CInherited {
    String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;
}

<span class="hljs-meta">@AInherited("父类的AInherited")</span>
<span class="hljs-meta">@BInherited("父类的BInherited")</span>
<span class="hljs-meta">@CInherited("父类的CInherited")</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">SuperClass</span> {
}

<span class="hljs-meta">@BInherited("子类的BInherited")</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildClass</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">SuperClass</span> {
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    Annotation[] annotations = ChildClass.class.getAnnotations();
    System.out.println(Arrays.toString(annotations));
    <span class="hljs-comment">// output: [@annotations.InheritedTest1$AInherited(value=父类的AInherited), @annotations.InheritedTest1$BInherited(value=子类的BInherited)]</span>
}

}

说明:

  1. 自定义注解 @CInherited 没有被 @Inherited 修饰,不具备继承性,子类ChildClass获取类上的注解时,没有该注解;
  2. 自定义注解@BInherited,具备继承性,但是子类 ChildClass 在类上自行指定了与父类相同类型的注解 @BInherited,那么子类获取其类注解时,@BInherited 为子类自己声明的;
  3. 自定义注解@AInherited,具备继承性,子类上未指定相同注解,子类获取注解时,成功获取到父类上的 @AInherited 注解。

二、基于类继承

属性和方法注解的继承,与类注解的继承完全不同,与元注解 Inherited 毫无关系,忠实于方法 / 属性本身的继承。

以下示例说明属性 / 方法注解的继承:

public class InheritedTest {
<span class="hljs-meta">@Target(value = {ElementType.METHOD, ElementType.FIELD})</span>
<span class="hljs-meta">@Retention(value = RetentionPolicy.RUNTIME)</span>
<span class="hljs-meta">@interface</span> DESC {
    String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;
}

<span class="hljs-keyword">class</span> <span class="hljs-title class_">SuperClass</span> {
    <span class="hljs-meta">@DESC("父类方法foo")</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">()</span> {}
    <span class="hljs-meta">@DESC("父类方法bar")</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">bar</span><span class="hljs-params">()</span>{}
    <span class="hljs-meta">@DESC("父类的属性")</span>
    <span class="hljs-keyword">public</span> String field;
}

<span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildClass</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">SuperClass</span> {
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">()</span> {
        <span class="hljs-built_in">super</span>.foo();
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> NoSuchMethodException, NoSuchFieldException {
    <span class="hljs-type">Method</span> <span class="hljs-variable">foo</span> <span class="hljs-operator">=</span> ChildClass.class.getMethod(<span class="hljs-string">"foo"</span>);
    System.out.println(Arrays.toString(foo.getAnnotations()));
    <span class="hljs-comment">// output: []</span>
    <span class="hljs-comment">// 子类ChildClass重写了父类方法foo,并且@Override注解只在源码阶段保留,所以没有任何注解</span>

    <span class="hljs-type">Method</span> <span class="hljs-variable">bar</span> <span class="hljs-operator">=</span> ChildClass.class.getMethod(<span class="hljs-string">"bar"</span>);
    System.out.println(Arrays.toString(bar.getAnnotations()));
    <span class="hljs-comment">// output: [@annotations.InheritedTest$DESC(value=父类方法bar)]</span>
    <span class="hljs-comment">// bar方法未被子类重写,从父类继承到了原本注解</span>

    <span class="hljs-type">Field</span> <span class="hljs-variable">field</span> <span class="hljs-operator">=</span> ChildClass.class.getField(<span class="hljs-string">"field"</span>);
    System.out.println(Arrays.toString(field.getAnnotations()));
}
<span class="hljs-comment">// output: [@annotations.InheritedTest$DESC(value=父类的属性)]</span>
<span class="hljs-comment">// 解释同上</span>

三、基于接口继承 / 实现

基于接口的继承 / 实现中,属性和方法注解的继承大体与类相似。jdk7 以前接口的方法都需要实现,所以子类中的方法永远也无法获得父接口方法的注解,但是 jdk8 以后的默认方法打开了这种限制。

以下以 demo 说明:

public class IterInheritedTest {
<span class="hljs-meta">@Target(value = {ElementType.METHOD, ElementType.FIELD})</span>
<span class="hljs-meta">@Retention(value = RetentionPolicy.RUNTIME)</span>
<span class="hljs-meta">@interface</span> DESC {
    String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;
}

<span class="hljs-keyword">interface</span> <span class="hljs-title class_">SuperInterface</span> {
    <span class="hljs-meta">@DESC("父接口的属性")</span>
    <span class="hljs-type">String</span> <span class="hljs-variable">field</span> <span class="hljs-operator">=</span> <span class="hljs-string">"field"</span>;
    <span class="hljs-meta">@DESC("父接口方法foo")</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">()</span>;
    <span class="hljs-meta">@DESC("父接口方法bar")</span>
    <span class="hljs-keyword">default</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">bar</span><span class="hljs-params">()</span> {

    }
}

<span class="hljs-keyword">interface</span> <span class="hljs-title class_">ChildInterface</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">SuperInterface</span> {
    <span class="hljs-meta">@DESC("子接口方法foo")</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">()</span>;
}

<span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildClass</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">SuperInterface</span> {
    <span class="hljs-meta">@DESC("子类的属性")</span>
    <span class="hljs-keyword">public</span> <span class="hljs-type">String</span> <span class="hljs-variable">field</span> <span class="hljs-operator">=</span> <span class="hljs-string">"field"</span>;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">foo</span><span class="hljs-params">()</span> {
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> NoSuchMethodException, NoSuchFieldException {
    <span class="hljs-type">Method</span> <span class="hljs-variable">iFoo</span> <span class="hljs-operator">=</span> ChildInterface.class.getMethod(<span class="hljs-string">"foo"</span>);
    System.out.println(Arrays.toString(iFoo.getAnnotations()));
    <span class="hljs-comment">// output: [@annotations.IterInheritedTest$DESC(value=子接口方法foo)]</span>

    <span class="hljs-type">Method</span> <span class="hljs-variable">iBar</span> <span class="hljs-operator">=</span> ChildInterface.class.getMethod(<span class="hljs-string">"bar"</span>);
    System.out.println(Arrays.toString(iBar.getAnnotations()));
    <span class="hljs-comment">// output: [@annotations.IterInheritedTest$DESC(value=父接口方法bar)]</span>

    <span class="hljs-type">Field</span> <span class="hljs-variable">iField</span> <span class="hljs-operator">=</span> ChildInterface.class.getField(<span class="hljs-string">"field"</span>);
    System.out.println(Arrays.toString(iField.getAnnotations()));
    <span class="hljs-comment">// output: [@annotations.IterInheritedTest$DESC(value=父接口的属性)]</span>

    <span class="hljs-type">Method</span> <span class="hljs-variable">foo</span> <span class="hljs-operator">=</span> ChildClass.class.getMethod(<span class="hljs-string">"foo"</span>);
    System.out.println(Arrays.toString(foo.getAnnotations()));
    <span class="hljs-comment">// output: []; 被子类覆盖</span>

    <span class="hljs-type">Method</span> <span class="hljs-variable">bar</span> <span class="hljs-operator">=</span> ChildClass.class.getMethod(<span class="hljs-string">"bar"</span>);
    System.out.println(Arrays.toString(bar.getAnnotations()));
    <span class="hljs-comment">// output: [@annotations.IterInheritedTest$DESC(value=父接口方法bar)]</span>

    <span class="hljs-type">Field</span> <span class="hljs-variable">field</span> <span class="hljs-operator">=</span> ChildClass.class.getField(<span class="hljs-string">"field"</span>);
    System.out.println(Arrays.toString(field.getAnnotations()));
    <span class="hljs-comment">// output: [@annotations.IterInheritedTest$DESC(value=子类的属性)]</span>
    <span class="hljs-comment">// 是子类作用域下的属性`field`</span>
}

}

总结

@Inherited 修饰的注解,继承性只体现在对类的修饰上;
方法和属性上注解的继承,忠实于方法 / 属性继承本身,客观反映方法 / 属性上的注解。