天天看點

Java高品質代碼之 — 泛型與反射

在Java5後推出了泛型,使我們在編譯期間操作集合或類時更加的安全,更友善代碼的閱讀,而讓身為編譯性語言的Java提供動态性的反射技術,更是在架構開發中大行其道,進而讓Java活起來,下面看一下在使用泛型和反射需要注意和了解的事情 

1.Java的泛型是類型擦除的

      Java中的泛型是在編譯期間有效的,在運作期間将會被删除,也就是所有泛型參數類型在編譯後都會被清除掉.請看以下例子 

Java代碼   

Java高品質代碼之 — 泛型與反射

  1. publicstaticvoid test(List  testParameter) {  
  2. }  

      以上是一個最最簡單的重載例子,在編譯後泛型類型是會被擦除的,是以無論是List 或List 都會變成List ,是以參數相同,重載不成立,無法編譯通過,而且讀者可以嘗試将不同泛型的類getClass()看看,他們的Class是一樣的  

2.不能初始化泛型參數和數組

      請觀察以下代碼 

Java高品質代碼之 — 泛型與反射
  1. class Test  {  
  2. //編譯不通過
  3. private T t = new T();  
  4. private T[] tArray = new T[5];  
  5. //編譯通過
  6. 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高品質代碼之 — 泛型與反射
  1. publicstaticvoid main(String[] args) {  
  2. // 編譯不通過
  3.     List
  4. // or

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

Java高品質代碼之 — 泛型與反射
  1. //Number 為Integer 和 Double 的公共父類
  2.     List  numberList =  new ArrayList ();  
  3. //使用通配符,代表實際類型是Number類型的子類
  4.     Listextends Number> numberList2 = new ArrayList ();  
  5. //or
  6.     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高品質代碼之 — 泛型與反射
  1. 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的方法,請看以下例子 

Java高品質代碼之 — 泛型與反射
  1.     Class  cls = User. class;  
  2. //擷取類方法
  3.     cls.getDeclaredMethods();  
  4.     cls.getMethods();  
  5. //擷取類構造函數
  6.     cls.getDeclaredConstructors();  
  7.     cls.getConstructors();  
  8. //擷取類屬性
  9.     cls.getDeclaredFields();  
  10.     cls.getFields();  

      getXXX的方式是擷取所有公共的(public)級别的,包括從父類繼承的方法,而getDeclaredXXX的方式是擷取所有的,包括公共的(public),私有的(private),不受限與通路權限, 

10.反射通路屬性或方法時将Accessible設定為true

      在調用構造函數或方法的invoke前檢查accessible已經是公認的寫法,例如以下代碼 

Java高品質代碼之 — 泛型與反射
  1. publicstaticvoid main(String[] args) throws Exception {  
  2. //建立對象
  3.     User user = cls.newInstance();  
  4. //擷取test方法
  5.     Method method = cls.getDeclaredMethod("test");  
  6. //檢查Accessible屬性
  7. if(!method.isAccessible()){  
  8.         method.setAccessible(true);  
  9.     }  
  10.     method.invoke(user);  

      讀者可以嘗試擷取Class的getMethod,也就是公開的方法,再輸出isAccessible,可以看到輸出的其實也是false,其實因為accessible屬性的語義并不是我們了解的通路權限,而是指是否進行安全檢查,而安全監察是非常消耗資源的,是以反射提供了Accessible可選項,讓開發者逃避安全檢查,有興趣的讀者可以檢視AccessibleObject類觀察其源碼了解安全檢查. 

11.使用forName動态加載類

      forName相信各位讀者不會陌生,在使用JDBC時要動态加載資料庫驅動就是使用forName的方式進行加載,同時亦可以從外部配置檔案中讀取類的全路徑字元串進行加載,在使用forName時,被加載的類就會被加載到記憶體當中,隻會加載類,并不會執行任何代碼,而我們的資料庫驅動就是利用static代碼塊來執行操作的,因為當類被加載到記憶體中時,會執行static代碼塊 

12.使用反射讓模闆方法更強大

      模闆方法的定義是,定義一個操作的算法骨架,将某些步驟延遲到子類當中實作,而實作細節由子類決定,父類隻決定骨架,以下是一個傳統模闆方法的事例 

Java高品質代碼之 — 泛型與反射
  1. publicabstractclass Test {  
  2. publicfinalvoid doSomething() {  
  3.         System.out.println("start...");  
  4.         doInit();  
  5.         System.out.println("end.....");  
  6. protectedabstractvoid doInit();  

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

Java高品質代碼之 — 泛型與反射
  1. publicfinalvoid doSomething() throws Exception {  
  2.         Method[] methods = this.getClass().getDeclaredMethods();  
  3. for (Method method : methods) {  
  4. if (this.checkInitMethod(method)) {  
  5.                 method.invoke(this);  
  6.             }  
  7.         }  
  8. privateboolean checkInitMethod(Method method) {  
  9. // 方法名初始是否為init
  10. return method.getName().startsWith("init")  
  11. // 是否為public修飾
  12.                 && Modifier.isPublic(method.getModifiers())  
  13. // 傳回值是否為void
  14.                 && method.getReturnType().equals(Void.TYPE)  
  15. // 是否沒有參數
  16.                 && !method.isVarArgs()  
  17. // 是否抽象類型
  18.                 && !Modifier.isAbstract(method.getModifiers());  

      看到上面的代碼,讀者是否有似曾相識的感覺?在使用Junit3時是不是隻要遵守方法的簽名約定,就能成為測試類?使用這種反射可以讓模闆方法更強大,下次需要使用多個方法在模闆方法中時,不要建立多個抽象方法,嘗試使用以上方式 

13.不要過分關注反射的效率

      反射的效率是一個老生常談的問題,普通的調用方法,建立類,在反射的情況下需要調用諸多API才能實作,效率當然要比普通情況下低下,但在項目當中真正引起性能問題的地方,絕大數不會是由反射引起的,而反射帶給我們的卻是如此的美妙,當今的各系列開源架構,幾乎都存在反射的身影,而且大量存在更比比皆是,讓Java這個沉睡的美女活起來的,非反射莫屬 

總結: 

      筆者在本文章中隻從《改善Java程式的151建議》中提取部分進行歸納性叙述,推薦各位讀者購買這本書,該書不僅從事例中學習,而且涉及到原理,底層的實作,不僅告訴你應該怎麼做,還告訴你為什麼要這樣做.