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

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


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

Java代碼  收藏代碼
  1. public static void test(List<Integer> testParameter) {  
  2.       
  3. }  
  4.   
  5. public static void test(List<String> testParameter) {  
  6.   
  7. }  


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


2.不能初始化泛型參數和數組 
      請觀察以下代碼 

Java代碼  收藏代碼
  1. class Test<T> {  
  2.     //編譯不通過  
  3.     private T t = new T();  
  4.     //編譯不通過  
  5.     private T[] tArray = new T[5];  
  6.     //編譯通過  
  7.     private List<T> list = new ArrayList<T>();  
  8. }  


      Java語言是一門強類型,編譯型的安全語言,它需要確保運行期的穩定性和安全性,所以在編譯時編譯器需要嚴格的檢查我們所聲明的類型,在編譯期間編譯器需要獲取T類型,但泛型在編譯期間已經被擦除了,所以new T()和new T[5]都會出現編譯錯誤,而爲什麼ArrayList卻能成功初始化?這是因爲ArrayList表面是泛型,但在編譯期間已經由ArrayList內部轉型成爲了Object,有興趣的讀者可以看一下ArrayList的源碼,源碼中容納元素的是private transient Object[] elementData,是Object類型的數組 


3.強制聲明泛型的實際類型 
      在使用泛型時,在大多數情況下應該聲明泛型的類型,例如該集合是存放User對象的,就應該使用List<User>來聲明,這樣可以儘量減少類型的轉換,若只使用List而不聲明泛型,則該集合等同於List<Object> 


4.注意泛型沒有繼承說 
      請看以下例子 

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.     // 編譯不通過  
  3.     List<Object> list = new ArrayList<Integer>();  
  4.     // or  
  5.     List<Object> list = new ArrayList<Double>();  
  6. }  


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

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.     //Number 爲Integer 和 Double 的公共父類  
  3.     List<Number> numberList = new ArrayList<Number>();  
  4.       
  5.     //使用通配符,代表實際類型是Number類型的子類  
  6.     List<? extends Number> numberList2 = new ArrayList<Integer>();  
  7.     //or  
  8.     List<? extends Number> numberList3 = new ArrayList<Double>();  
  9. }  


5.不同場景使用不同的通配符 
     泛型當中支持通配符,可以單獨使用 '?' 來表示任意類型,也可以使用剛纔上面例子中的extends關鍵字表示傳入參數是某一個類或接口的子類,也可以使用super關鍵字表示接受某一個類型的父類型,extends和super的寫法一樣,extends是限定上界,super爲限定下界 


6.建議採用順序爲List<T> List<?> List<Object> 
      以上三者都可以容納所有的對象,但使用的順序應該是首選List<T>,然後List<?>,最後才使用List<Object>,原因是List<T>是確定爲某一類型的,安全的,也是Java所推薦的,而List<?>代表爲任意類型,與List<T>類似,而List<Object>中全部爲Object類型,這與不使用泛型無異,而List<T>是可讀可寫的,因爲已經明確了T類型,而其他兩者在讀取時需要進行向下轉型,造成了不必要的轉型 


7.使用多重邊界限定 
      現在有一個業務需求,收錢時需要是我們本公司的員工(繼承User),同時亦需要具備收銀員的功能(實現Cashier),此時我們當然可以想到接受1個User然後判斷User是否實現了Cashier接口,但在泛型的世界中,我們可以怎麼做?請看以下例子 

Java代碼  收藏代碼
  1. public static  <T extends User & Cashier> void CollectMoney(T t){  
  2.   
  3. }  


      以上例子就表明,限定了上界,只要是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. public static void main(String[] args) {  
  2.     Class<User> cls = User.class;  
  3.     //獲取類方法  
  4.     cls.getDeclaredMethods();  
  5.     cls.getMethods();  
  6.     //獲取類構造函數  
  7.     cls.getDeclaredConstructors();  
  8.     cls.getConstructors();  
  9.     //獲取類屬性  
  10.     cls.getDeclaredFields();  
  11.     cls.getFields();  
  12. }  


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


  1.  Type[] t1 = m.getParameterTypes();// 其返回是參數的類型  
  2.         Type[] t2 = m.getGenericParameterTypes();//其返回的是參數的參數化的類型,裏面的帶有實際的參數類型



10.反射訪問屬性或方法時將Accessible設置爲true 
      在調用構造函數或方法的invoke前檢查accessible已經是公認的寫法,例如以下代碼 

Java代碼  收藏代碼
  1. public static void main(String[] args) throws Exception {  
  2.     Class<User> cls = User.class;  
  3.     //創建對象  
  4.     User user = cls.newInstance();  
  5.       
  6.     //獲取test方法  
  7.     Method method = cls.getDeclaredMethod("test");  
  8.       
  9.     //檢查Accessible屬性  
  10.     if(!method.isAccessible()){  
  11.         method.setAccessible(true);  
  12.     }  
  13.     method.invoke(user);  
  14. }  


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

11.使用forName動態加載類 
      forName相信各位讀者不會陌生,在使用JDBC時要動態加載數據庫驅動就是使用forName的方式進行加載,同時亦可以從外部配置文件中讀取類的全路徑字符串進行加載,在使用forName時,被加載的類就會被加載到內存當中,只會加載類,並不會執行任何代碼,而我們的數據庫驅動就是利用static代碼塊來執行操作的,因爲當類被加載到內存中時,會執行static代碼塊 


12.使用反射讓模板方法更強大 
      模板方法的定義是,定義一個操作的算法骨架,將某些步驟延遲到子類當中實現,而實現細節由子類決定,父類只決定骨架,以下是一個傳統模板方法的事例 

Java代碼  收藏代碼
  1. public abstract class Test {  
  2.   
  3.     public final void doSomething() {  
  4.         System.out.println("start...");  
  5.         doInit();  
  6.         System.out.println("end.....");  
  7.     }  
  8.   
  9.     protected abstract void doInit();  
  10. }  


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

Java代碼  收藏代碼
  1. public abstract class Test {  
  2.   
  3.     public final void doSomething() throws Exception {  
  4.   
  5.         Method[] methods = this.getClass().getDeclaredMethods();  
  6.         System.out.println("start...");  
  7.         for (Method method : methods) {  
  8.             if (this.checkInitMethod(method)) {  
  9.                 method.invoke(this);  
  10.             }  
  11.         }  
  12.         System.out.println("end.....");  
  13.     }  
  14.   
  15.     private boolean checkInitMethod(Method method) {  
  16.         // 方法名初始是否爲init  
  17.         return method.getName().startsWith("init")  
  18.         // 是否爲public修飾  
  19.                 && Modifier.isPublic(method.getModifiers())  
  20.                 // 返回值是否爲void  
  21.                 && method.getReturnType().equals(Void.TYPE)  
  22.                 // 是否沒有參數  
  23.                 && !method.isVarArgs()  
  24.                 // 是否抽象類型  
  25.                 && !Modifier.isAbstract(method.getModifiers());  
  26.     }  
  27. }  


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


13.不要過分關注反射的效率 
      反射的效率是一個老生常談的問題,普通的調用方法,創建類,在反射的情況下需要調用諸多API才能實現,效率當然要比普通情況下低下,但在項目當中真正引起性能問題的地方,絕大數不會是由反射引起的,而反射帶給我們的卻是如此的美妙,當今的各系列開源框架,幾乎都存在反射的身影,而且大量存在更比比皆是,讓Java這個沉睡的美女活起來的,非反射莫屬 
發佈了128 篇原創文章 · 獲贊 36 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章