讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議90:小心註解繼承 >

建議90:小心註解繼承

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元註解的弊大於利,特別是一個類的繼承層次較深時,如果註解較多,則很難判斷出是哪個註解對子類產生了邏輯劫持。