Java從1.5版開始引入了註解(Annotation),其目的是在不影響代碼語義的情況下增強代碼的可讀性,並且不改變代碼的執行邏輯,對於註解始終有兩派爭論,正方認為註解有益於數據與代碼的耦合,「在有代碼的周邊集合數據」;反方認為註解把代碼和數據混淆在一起,增加了代碼的易變性,削弱了程序的健壯性和穩定性。這些爭論暫且不表,我們要說的是一個我們不常用的元註解(Meta-Annotation):@Inherited,它表示一個註解是否可以自動被繼承,我們來看它應如何使用。
思考一個例子,比如描述鳥類,它有顏色、體型、習性等屬性,我們以顏色為例,定義一個註解來修飾一下,代碼如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@interface Desc{
enum Color{
White, Grayish, Yellow;
}
//默認顏色是白色的
Color c()default Color.White;
}
該註解Desc前增加了三個元註解:Retention表示的是該註解的保留級別,Target表示的是該註解可以標注在什麼地方,@Inherited表示該註解會被自動繼承。註解定義完畢,我們把它標注在類上,代碼如下:
@Desc(c=Color.White)
abstract class Bird{
//鳥的顏色
public abstract Color getColor();
}
//麻雀
class Sparrow extends Bird{
private Color color;
//默認是淺灰色
public Sparrow(){
color=Color.Grayish;
}
//構造函數定義鳥的顏色
public Sparrow(Color_color){
color=_color;
}
@Override
public Color getColor(){
return color;
}
}
//鳥巢,工廠方法模式
enum BirdNest{
Sparrow;
//鳥類繁殖
public Bird reproduce(){
Desc bd=Sparrow.class.getAnnotation(Desc.class);
return bd==null?new Sparrow():new Sparrow(bd.c());
}
}
程序比較簡單,聲明了一個Bird抽像類,並且標注了Desc註解,描述鳥類的顏色是白色的,然後編寫了一個麻雀Sparrow類,它有兩個構造函數,一個是默認的構造函數,也就是我們經常看到的麻雀是淺灰色的,另外一個構造函數是自定義麻雀的顏色,之後又定義了一個鳥巢(工廠方法模式),它是專門負責鳥類繁殖的,它的生產方法reproduce會根據實現類註解信息生成不同顏色的麻雀。我們編寫一個客戶端調用,代碼如下:
public static void main(Stringargs){
Bird bird=BirdNest.Sparrow.reproduce();
Color color=bird.getColor();
System.out.println("Bird's color is:"+color);
}
現在的問題是這段客戶端程序會打印出什麼來?因為採用了工廠方法模式,它最主要的問題就是bird變量到底採用了哪個構造函數來生成,是無參構造還是有參構造?如果我們單獨看子類Sparrow,它沒有被添加任何註解,那工廠方法中的bd變量就應該是null了,應該調用的是無參構造。是不是如此呢?我們來看運行結果:
Bird's color is:White
白色?這是我們添加到父類Bird上的顏色,為什麼?就是因為我們在註解上加了@Inherited註解,它表示的意思是我們只要把註解@Desc加到父類Bird上,它的所有子類都會自動從父類繼承@Desc註解,不需要顯式聲明,這與Java類的繼承有點不同,若Sparrow類繼承了Bird,則必須使用extends關鍵字聲明,而Bird上的註解@Desc繼承自Bird卻不用顯式聲明,只要聲明@Desc註解是可自動繼承的即可。
採用@Inherited元註解有利有弊,利的地方是一個註解只要標注到父類,所有的子類都會自動具有與父類相同的註解,整齊、統一而且便於管理,弊的地方是單單閱讀子類代碼,我們無從知道為何邏輯會被改變,因為子類沒有明顯標注該註解。總體上來說,使用@Inherited元註解的弊大於利,特別是一個類的繼承層次較深時,如果註解較多,則很難判斷出是哪個註解對子類產生了邏輯劫持。