在Java5後推出了泛型,使我們在編譯期間操作集合或類時更加的安全,更友善代碼的閱讀,而讓身為編譯性語言的Java提供動态性的反射技術,更是在架構開發中大行其道,進而讓Java活起來,下面看一下在使用泛型和反射需要注意和了解的事情
1.Java的泛型是類型擦除的
Java中的泛型是在編譯期間有效的,在運作期間将會被删除,也就是所有泛型參數類型在編譯後都會被清除掉.請看以下例子
Java代碼

- publicstaticvoid test(List testParameter) {
- }
以上是一個最最簡單的重載例子,在編譯後泛型類型是會被擦除的,是以無論是List 或List 都會變成List ,是以參數相同,重載不成立,無法編譯通過,而且讀者可以嘗試将不同泛型的類getClass()看看,他們的Class是一樣的
2.不能初始化泛型參數和數組
請觀察以下代碼

- 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.注意泛型沒有繼承說
請看以下例子

- publicstaticvoid main(String[] args) {
- // 編譯不通過
- List
- // or
上面代碼的業務邏輯為有一組元素,但我不确定元素時整形還是浮點型,當我在确定後建立對應的泛型實作類,剛接觸泛型的讀者有可能會犯以上錯誤,了解為泛型之間是可以繼承的,例如聲明Object的泛型實際上建立Integer泛型,錯誤認為泛型之間也存在繼承關系,但這是不正确的,泛型是幫助開發者編譯期間類型檢查安全.我們可以換種方式實作以上業務邏輯

- //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接口,但在泛型的世界中,我們可以怎麼做?請看以下例子

- 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類還可以描述基本資料類型,由于基本類型并不是Java中的對象,它們
一般存在于棧,但Class仍然可以對它們進行描述,例如使用int.class
3.其對象都是單例模式,一個Class的實作對象描述一個類,并且隻描述一個類
是以隻要是該被描述的類所有對象都隻有一個Class執行個體
4.Class類是Java反射的入口
9.适時選擇getDeclaredXXX和getXXX
Class類中提供了很多getDeclaredXXX和getXXX的方法,請看以下例子

- 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已經是公認的寫法,例如以下代碼

- publicstaticvoid main(String[] args) throws Exception {
- //建立對象
- 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.使用反射讓模闆方法更強大
模闆方法的定義是,定義一個操作的算法骨架,将某些步驟延遲到子類當中實作,而實作細節由子類決定,父類隻決定骨架,以下是一個傳統模闆方法的事例

- publicabstractclass Test {
- publicfinalvoid doSomething() {
- System.out.println("start...");
- doInit();
- System.out.println("end.....");
- protectedabstractvoid doInit();
此時子類隻需要繼承Test類實作doInit()方法即可嵌入到doSomething中,現在我們有一個需求,若我在doSomething中需要調用一系列的方法才能完成doSomething呢?而且我調用方法的數量并不确定,隻需遵從某些規則則可将方法添加到doSomething方法當中.請看以下代碼

- publicfinalvoid doSomething() throws Exception {
- Method[] methods = this.getClass().getDeclaredMethods();
- for (Method method : methods) {
- if (this.checkInitMethod(method)) {
- method.invoke(this);
- }
- }
- 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建議》中提取部分進行歸納性叙述,推薦各位讀者購買這本書,該書不僅從事例中學習,而且涉及到原理,底層的實作,不僅告訴你應該怎麼做,還告訴你為什麼要這樣做.