標籤:
在Java5後推出了泛型,使我們在編譯期間操作集合或類時更加的安全,更方便代碼的閱讀,而讓身為編譯性語言的Java提供動態性的反射技術,更是在架構開發中大行其道,從而讓Java活起來,下面看一下在使用泛型和反射需要注意和瞭解的事情?
1.Java的泛型是類型擦除的
????? Java中的泛型是在編譯期間有效,在運行期間將會被刪除,也就是所有泛型參數類型在編譯後都會被清除掉.請看以下例子?
Java代碼???
- publicstaticvoid?test(List??testParameter)?{??
- ?
- }??
- ?
- publicstaticvoid?test(List??testParameter)?{??
- ?
- }??
????? 以上是一個最最簡單的重載例子,在編譯後泛型型別是會被擦除的,所以無論是List?或List?都會變成List?,所以參數相同,重載不成立,無法編譯通過,而且讀者可以嘗試將不同泛型的類getClass()看看,他們的Class是一樣的??
2.不能初始化泛型參數和數組
????? 請觀察以下代碼?
Java代碼???
- class?Test??{??
- //編譯不通過
- private?T?t?=?new?T();??
- //編譯不通過
- private?T[]?tArray?=?new?T[5];??
- //編譯通過
- private?List??list?=??new?ArrayList?();??
- }??
????? Java語言是一門強型別,編譯型的安全語言,它需要確保運行期的穩定性和安全性,所以在編譯時間編譯器需要嚴格的檢查我們所聲明的類型,在編譯期間編譯器需要擷取T類型,但泛型在編譯期間已經被擦除了,所以new T()和new T[5]都會出現編譯錯誤,而為什麼ArrayList卻能成功初始化?這是因為ArrayList表面是泛型,但在編譯期間已經由ArrayList內部轉型成為了Object,有興趣的讀者可以看一下ArrayList的源碼,源碼中容納元素的是private transient Object[] elementData,是Object類型的數組?
3.強制聲明泛型的實際類型
????? 在使用泛型時,在大多數情況下應該聲明泛型的類型,例如該集合是存放User對象的,就應該使用List?來聲明,這樣可以盡量減少類型的轉換,若只使用List而不聲明泛型,則該集合等同於List?
4.注意泛型沒有繼承說
????? 請看以下例子?
Java代碼???
- publicstaticvoid?main(String[]?args)?{??
- //?編譯不通過
- ????List
- //?or
- ????List
- }??
????? 上面代碼的商務邏輯為有一組元素,但我不確定元素時整形還是浮點型,當我在確定後建立對應的泛型實作類別,剛接觸泛型的讀者有可能會犯以上錯誤,理解為泛型之間是可以繼承的,例如聲明Object的泛型實際上建立Integer泛型,錯誤認為泛型之間也存在繼承關係,但這是不正確的,泛型是協助開發人員編譯期間類型檢查安全.我們可以換種方式實現以上商務邏輯?
Java代碼???
- publicstaticvoid?main(String[]?args)?{??
- //Number?為Integer?和?Double?的公用父類
- ????List??numberList?=??new?ArrayList?();??
- ?
- //使用萬用字元,代表實際類型是Number類型的子類
- ????Listextends?Number>?numberList2?=?new?ArrayList?();??
- //or
- ????Listextends?Number>?numberList3?=?new?ArrayList?();??
- }??
5.不同情境使用不同的萬用字元
???? 泛型當中支援萬用字元,可以單獨使用 ‘?‘ 來表示任意類型,也可以使用剛才上面例子中的extends關鍵字表示傳入參數是某一個類或介面的子類,也可以使用super關鍵字表示接受某一個類型的父類型,extends和super的寫法一樣,extends是限定上界,super為限定下界?
6.建議採用順序為List?List List?
????? 以上三者都可以容納所有的對象,但使用的順序應該是首選List?,然後List,最後才使用List?
7.使用多重邊界限定
????? 現在有一個業務需求,收錢時需要是我們本公司的員工(繼承User),同時亦需要具備收銀員的功能(實現Cashier),此時我們當然可以想到接受1個User然後判斷User是否實現了Cashier介面,但在泛型的世界中,我們可以怎麼做?請看以下例子?
Java代碼???
- publicstatic???extends?User?&?Cashier>??void?CollectMoney(T?t){??
- ?
- }??
????? 以上例子就表明,限定了上界,只要是User和Cashier的子類才可以作為參數傳遞到方法當中?
======================我是泛型和反射的分界線====================
8.注意Class類的特殊性
????? Java語言是先把Java源檔案編譯成尾碼為class的位元組碼檔案,然後通過ClassLoader機制把類檔案載入到記憶體當中,最後產生執行個體執行的,在描述一個類是,Java使用了一個元類來對類進行描述,這就是Class類,他是一個描述類的類,所以註定Class類是特殊的?
????? 1.Class類無建構函式,Class對象載入時由JVM通過調用類載入器的?
??????? defineClass方法來構造Class對象?
????? 2.Class類還可以描述基礎資料型別 (Elementary Data Type),由於基本類型並不是Java中的對象,它們?
??????? 一般存在於棧,但Class仍然可以對它們進行描述,例如使用int.class?
????? 3.其對象都是單例模式,一個Class的實現對象描述一個類,並且只描述一個類?
??????? 所以只要是該被描述的類所有對象都只有一個Class執行個體?
????? 4.Class類是Java反射的入口?
9.適時選擇getDeclaredXXX和getXXX
????? Class類中提供了很多getDeclaredXXX和getXXX的方法,請看以下例子?
Java代碼???
- publicstaticvoid?main(String[]?args)?{??
- ????Class??cls?=?User.?class;??
- //擷取類方法
- ????cls.getDeclaredMethods();??
- ????cls.getMethods();??
- //擷取類建構函式
- ????cls.getDeclaredConstructors();??
- ????cls.getConstructors();??
- //擷取類屬性
- ????cls.getDeclaredFields();??
- ????cls.getFields();??
- }??
????? getXXX的方式是擷取所有公用的(public)層級的,包括從父類繼承的方法,而getDeclaredXXX的方式是擷取所有的,包括公用的(public),私人的(private),不受限與存取權限,?
10.反射訪問屬性或方法時將Accessible設定為true
????? 在調用建構函式或方法的invoke前檢查accessible已經是公認的寫法,例如以下代碼?
Java代碼???
- publicstaticvoid?main(String[]?args)?throws?Exception?{??
- ????Class??cls?=?User.?class;??
- //建立對象
- ????User?user?=?cls.newInstance();??
- ?
- //擷取test方法
- ????Method?method?=?cls.getDeclaredMethod("test");??
- ?
- //檢查Accessible屬性
- if(!method.isAccessible()){??
- ????????method.setAccessible(true);??
- ????}??
- ????method.invoke(user);??
- }??
????? 讀者可以嘗試擷取Class的getMethod,也就是公開的方法,再輸出isAccessible,可以看到輸出的其實也是false,其實因為accessible屬性的語義並不是我們理解的存取權限,而是指是否進行安全檢查,而安全監察是非常消耗資源的,所以反射提供了Accessible可選項,讓開發人員逃避安全檢查,有興趣的讀者可以查看AccessibleObject類觀察其源碼瞭解安全檢查.?
11.使用forName動態載入類
????? forName相信各位讀者不會陌生,在使用JDBC時要動態載入資料庫驅動就是使用forName的方式進行載入,同時亦可以從外部設定檔中讀取類的全路徑字串進行載入,在使用forName時,被載入的類就會被載入到記憶體當中,只會載入類,並不會執行任何代碼,而我們的資料庫驅動就是利用static代碼塊來執行操作的,因為當類被載入到記憶體中時,會執行static代碼塊?
12.使用反射讓模板方法更強大
????? 模板方法的定義是,定義一個操作的演算法骨架,將某些步驟延遲到子類當中實現,而實現細節由子類決定,父類只決定骨架,以下是一個傳統模板方法的案例?
Java代碼???
- publicabstractclass?Test?{??
- ?
- publicfinalvoid?doSomething()?{??
- ????????System.out.println("start...");??
- ????????doInit();??
- ????????System.out.println("end.....");??
- ????}??
- ?
- protectedabstractvoid?doInit();??
- }??
??? 此時子類只需要繼承Test類實現doInit()方法即可嵌入到doSomething中,現在我們有一個需求,若我在doSomething中需要調用一系列的方法才能完成doSomething呢?而且我調用方法的數量並不確定,只需遵從某些規則則可將方法添加到doSomething方法當中.請看以下代碼?
Java代碼???
- publicabstractclass?Test?{??
- ?
- publicfinalvoid?doSomething()?throws?Exception?{??
- ?
- ????????Method[]?methods?=?this.getClass().getDeclaredMethods();??
- ????????System.out.println("start...");??
- for?(Method?method?:?methods)?{??
- if?(this.checkInitMethod(method))?{??
- ????????????????method.invoke(this);??
- ????????????}??
- ????????}??
- ????????System.out.println("end.....");??
- ????}??
- ?
- privateboolean?checkInitMethod(Method?method)?{??
- //?方法名初始是否為init
- return?method.getName().startsWith("init")??
- //?是否為public修飾
- ????????????????&&?Modifier.isPublic(method.getModifiers())??
- //?傳回值是否為void
- ????????????????&&?method.getReturnType().equals(Void.TYPE)??
- //?是否沒有參數
- ????????????????&&?!method.isVarArgs()??
- //?是否抽象類別型
- ????????????????&&?!Modifier.isAbstract(method.getModifiers());??
- ????}??
- }??
????? 看到上面的代碼,讀者是否有似曾相識的感覺?在使用Junit3時是不是只要遵守方法的簽名約定,就能成為測試類別?使用這種反射可以讓模板方法更強大,下次需要使用多個方法在模板方法中時,不要建立多個抽象方法,嘗試使用以上方式?
13.不要過分關注反射的效率
????? 反射的效率是一個老生常談的問題,普通的調用方法,建立類,在反射的情況下需要調用諸多API才能實現,效率當然要比普通情況下低下,但在項目當中真正引起效能問題的地方,絕大數不會是由反射引起的,而反射帶給我們的卻是如此的美妙,當今的各系列開源架構,幾乎都存在反射的身影,而且大量存在更比比皆是,讓Java這個沉睡的美女活起來的,非反射莫屬?
總結:?
????? 筆者在本文章中只從《改善Java程式的151建議》中提取部分進行歸納性敘述,推薦各位讀者購買這本書,該書不僅從案例中學習,而且涉及到原理,底層的實現,不僅告訴你應該怎麼做,還告訴你為什麼要這樣做.?
Java高品質代碼之 — 泛型與反射