摘要
本文從三個方面介紹java注解的**“繼承性”**:
- 基於元注解@Inherited,類上注解的繼承性
- 基於類的繼承,方法/屬性上注解的繼承性
- 基於接口的繼承/實現,方法/屬性上注解的繼承性
一、基於@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 "";
}
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited // 聲明注解具有繼承性
@interface BInherited {
String value() default "";
}
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
// 未聲明注解具有繼承性
@interface CInherited {
String value() default "";
}
@AInherited("父類的AInherited")
@BInherited("父類的BInherited")
@CInherited("父類的CInherited")
class SuperClass {
}
@BInherited("子類的BInherited")
class ChildClass extends SuperClass {
}
public static void main(String[] args) {
Annotation[] annotations = ChildClass.class.getAnnotations();
System.out.println(Arrays.toString(annotations));
// output: [@annotations.InheritedTest1$AInherited(value=父類的AInherited), @annotations.InheritedTest1$BInherited(value=子類的BInherited)]
}
}
說明:
- 自定義注解
@CInherited
沒有被@Inherited 修飾,不具備繼承性,子類ChildClass
獲取類上的注解時,沒有該注解; - 自定義注解
@BInherited
,具備繼承性,但是子類ChildClass在類上自行指定了與父類相同類型的注解@BInherited,那么子類獲取其類注解時,@BInherited為子類自己聲明的; - 自定義注解
@AInherited
,具備繼承性,子類上未指定相同注解,子類獲取注解時,成功獲取到父類上的@AInherited注解。
二、基於類繼承
屬性和方法注解的繼承,與類注解的繼承完全不同,與元注解Inherited毫無關系,忠實於方法/屬性本身的繼承。
以下示例說明屬性/方法注解的繼承:
public class InheritedTest {
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface DESC {
String value() default "";
}
class SuperClass {
@DESC("父類方法foo")
public void foo() {}
@DESC("父類方法bar")
public void bar(){}
@DESC("父類的屬性")
public String field;
}
class ChildClass extends SuperClass {
@Override
public void foo() {
super.foo();
}
}
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
Method foo = ChildClass.class.getMethod("foo");
System.out.println(Arrays.toString(foo.getAnnotations()));
// output: []
// 子類ChildClass重寫了父類方法foo,並且@Override注解只在源碼階段保留,所以沒有任何注解
Method bar = ChildClass.class.getMethod("bar");
System.out.println(Arrays.toString(bar.getAnnotations()));
// output: [@annotations.InheritedTest$DESC(value=父類方法bar)]
// bar方法未被子類重寫,從父類繼承到了原本注解
Field field = ChildClass.class.getField("field");
System.out.println(Arrays.toString(field.getAnnotations()));
}
// output: [@annotations.InheritedTest$DESC(value=父類的屬性)]
// 解釋同上
三、基於接口繼承/實現
基於接口的繼承/實現中,屬性和方法注解的繼承大體與類相似。jdk7以前接口的方法都需要實現,所以子類中的方法永遠也無法獲得父接口方法的注解,但是jdk8以后的默認方法打開了這種限制。
以下以demo說明:
public class IterInheritedTest {
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface DESC {
String value() default "";
}
interface SuperInterface {
@DESC("父接口的屬性")
String field = "field";
@DESC("父接口方法foo")
public void foo();
@DESC("父接口方法bar")
default public void bar() {
}
}
interface ChildInterface extends SuperInterface {
@DESC("子接口方法foo")
@Override
void foo();
}
class ChildClass implements SuperInterface {
@DESC("子類的屬性")
public String field = "field";
@Override
public void foo() {
}
}
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
Method iFoo = ChildInterface.class.getMethod("foo");
System.out.println(Arrays.toString(iFoo.getAnnotations()));
// output: [@annotations.IterInheritedTest$DESC(value=子接口方法foo)]
Method iBar = ChildInterface.class.getMethod("bar");
System.out.println(Arrays.toString(iBar.getAnnotations()));
// output: [@annotations.IterInheritedTest$DESC(value=父接口方法bar)]
Field iField = ChildInterface.class.getField("field");
System.out.println(Arrays.toString(iField.getAnnotations()));
// output: [@annotations.IterInheritedTest$DESC(value=父接口的屬性)]
Method foo = ChildClass.class.getMethod("foo");
System.out.println(Arrays.toString(foo.getAnnotations()));
// output: []; 被子類覆蓋
Method bar = ChildClass.class.getMethod("bar");
System.out.println(Arrays.toString(bar.getAnnotations()));
// output: [@annotations.IterInheritedTest$DESC(value=父接口方法bar)]
Field field = ChildClass.class.getField("field");
System.out.println(Arrays.toString(field.getAnnotations()));
// output: [@annotations.IterInheritedTest$DESC(value=子類的屬性)]
// 是子類作用域下的屬性`field`
}
}
總結
@Inherited 修飾的注解,繼承性只體現在對類的修飾上;
方法和屬性上注解的繼承,忠實於方法/屬性繼承本身,客觀反映方法/屬性上的注解。