讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議109:不需要太多關注反射效率 >

建議109:不需要太多關注反射效率

反射的效率是一個老生常談的問題,有「經驗」的開發人員經常使用這句話恐嚇新人:反射的效率是非常低的,不到萬不得已就不要使用。事實上,這句話的前半句是對的,後半句是錯的。

反射的效率相對於正常的代碼執行確實低很多(經過測試,相差15倍左右),但是它是一個非常有效的運行期工具類,只要代碼結構清晰、可讀性好那就先開發起來,等到進行性能測試時證明此處性能確實有問題時再修改也不遲(一般情況下反射並不是性能的終極殺手,而代碼結構混亂、可讀性差則很可能會埋下性能隱患)。我們看這樣一個例子:在運行期獲得泛型類的泛型類型,代碼如下:


class Utils{

//獲得一個泛型類的實際泛型類型

public static<T>Class<T>getGenricClassType(Class clz){

Type type=clz.getGenericSuperclass();

if(type instanceof ParameterizedType){

ParameterizedType pt=(ParameterizedType)type;

Typetypes=pt.getActualTypeArguments();

if(types.length>0&&types[0]instanceof Class){

//若有多個泛型參數,依據位置索引返回

return(Class)types[0];

}

}

return(Class)Object.class;

}

}


前面我們講過,Java的泛型類型只存在於編譯期,那為什麼這個工具類可以取得運行期的泛型類型呢?那是因為該工具只支持繼承的泛型類,如果是在Java編譯時已經確定了泛型類的類型參數,那當然可以通過泛型獲得了。例如有這樣一個泛型類:


abstract class BaseDao<T>{

//獲得T的運行期類型

private Class<T>clz=Utils.getGenricClassType(getClass());

//根據主鍵獲得一條記錄

public void get(long id){

session.get(clz, id);

}

}

//操作user表

class UserDao extends BaseDao<String>{

}


對於UserDao類,編譯器編譯時已經明確了其參數類型是String,因此可以通過反射的方式來獲取其類型,這也是getGenricClassType方法使用的場景。

BaseDao和UserDao是ORM中的常客,BaseDao實現對數據庫的基本操作,比如增刪改查,而UserDao則是一個比較具體的數據庫操作,其作用是對User表進行操作,如果BaseDao能夠提供足夠多的基本方法,比如單表的增刪改查,那些與UserDao類似的BaseDao子類就可以省去大量的開發工作。但問題是持久層的session對像(這裡模擬的是Hibernate Session)需要明確一個具體的類型才能操作,比如get查詢,需要獲得兩個參數:實體類類型(用於確定映射的數據表)和主鍵,主鍵好辦,問題是實體類類型怎麼獲得呢?

子類自行傳遞?麻煩,而且也容易產生錯誤。

讀取配置問題?可行,但效率不高。

最好的辦法就是父類泛型化,子類明確泛型參數,然後通過反射讀取相應的類型即可,於是就有了我們代碼中的clz變量:通過反射獲得泛型類型。如此實現後,UserDao可以不用定義任何方法,繼承過來的父類操作方法已經能滿足基本需求了,這樣代碼結構清晰,可讀性又好。我已將這種方式使用在多個項目中了,目前沒有出現因為該反射引起的性能問題。

想想看,如果考慮反射效率問題,沒有clz變量,不使用反射,每個BaseDao的子類都要實現一個查詢操作,代碼將會大量重複,違反了"Don't Repeat Yourself"這條最基本的編碼規則,這會致使項目重構、優化難度加大,代碼的複雜度也會提高很多。

對於反射效率問題,不要做任何的提前優化和預期,這基本上是杞人憂天,很少有項目是因為反射問題引起系統效率故障的(除非是拷貝工的垃圾代碼,這不在我們的討論範圍之內),而且根據二八原則,80%的性能消耗在20%的代碼上,這20%的代碼才是我們關注的重點,不要單單把反射作為重點關注對象。

注意 反射效率低是個真命題,但因為這一點而不使用它就是個假命題。