1. 程式人生 > 程式設計 >深入分析JAVA 反射和泛型

深入分析JAVA 反射和泛型

從 JDK5 以後,Java 的 Class 類增加了泛型功能,從而允許使用泛型來限制 Class 類,例如,String.class 的型別實際上是 Class<String>。如果 Class 對應的類暫時未知,則使用 Class<?>。通過在反射中使用泛型,可以避兔使用反射生成的物件需要強制型別轉換。

泛型和 Class 類

使用 Class<T> 泛型可以避免強制型別轉換。例如,下面提供一個簡單的物件工廠,該物件工廠可以根據指定類來提供該類的例項。

public class CrazyitObjectFactory {
  public static Object getInstance(String clsName) {
    try {
      // 建立指定類對應的Class物件
      Class cls = Class.forName(clsName);
      // 返回使用該Class物件所建立的例項
      return cls.newInstance();
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
}

上面程式中兩行粗體字程式碼根據指定的字串型別建立了一個新物件,但這個物件的型別是 Object,因此當需要使用 CrazyitObjectFactory 的 getInstance() 方法來建立物件時,將會看到如下程式碼:

// 獲取例項後需要強制型別轉換
Date d = (Date)Crazyit.getInstance("java.util.Date");

甚至出現如下程式碼:

JFrame f = (JFrame)Crazyit.getInstance("java.util.Date");

上面程式碼在編譯時不會有任何問題,但執行時將丟擲 ClassCastException 異常,因為程式試圖將一個 Date 物件轉換成 JFrame 物件。

如果將上面的 CrazyitObjectFactory 工廠類改寫成使用泛型後的 Class,就可以避免這種情況。

public class CrazyitObjectFactory2 {
  public static <T> T getInstance(Class<T> cls) {
    try {
      return cls.newInstance();
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  public static void main(String[] args) {
    // 獲取例項後無須型別轉換
    Date d = CrazyitObjectFactory2.getInstance(Date.class);
    JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class);
  }
}

在上面程式的 getInstance() 方法中傳入一個 Class<T> 引數,這是一個泛型化的 Class 物件,呼叫該 Class 物件的 newInstance() 方法將返回一個 T 物件,如程式中粗體字程式碼所示。接下來當使用 CrazyitObjectFactory2 工廠類的 getInstance() 方法來產生物件時,無須使用強制型別轉換,系統會執行更嚴格的檢查,不會出現 ClassCastException 執行時異常。

前面介紹使用 Array 類來建立陣列時,曾經看到如下程式碼:

// 使用 Array 的 newInstance 方法來建立一個數組
Object arr = Array.newInstance(String.class,10);

對於上面的程式碼其實使用並不是非常方便,因為 newInstance() 方法返回的確實是一個 String[] 陣列,而不是簡單的 Object 物件。如果需要將物件當成 String[] 陣列使用,則必須使用強制型別轉換——這是不安全的操作。

為了示範泛型的優勢,可以對 Array 的 newInstance() 方法進行包裝。

public class CrazyitArray {
  // 對Array的newInstance方法進行包裝
  @SuppressWarnings("unchecked")
  public static <T> T[] newInstance(Class<T> componentType,int length) {
    return (T[]) Array.newInstance(componentType,length); // ①
  }

  public static void main(String[] args) {
    // 使用CrazyitArray的newInstance()建立一維陣列
    String[] arr = CrazyitArray.newInstance(String.class,10);
    // 使用CrazyitArray的newInstance()建立二維陣列
    // 在這種情況下,只要設定陣列元素的型別是int[]即可。
    int[][] intArr = CrazyitArray.newInstance(int[].class,5);
    arr[5] = "瘋狂Java講義";
    // intArr是二維陣列,初始化該陣列的第二個陣列元素
    // 二維陣列的元素必須是一維陣列
    intArr[1] = new int[] { 23,12 };
    System.out.println(arr[5]);
    System.out.println(intArr[1][1]);
  }
}

上面程式中粗體字程式碼定義的 newInstance() 方法對 Array 類提供的 newInstance() 方法進行了包裝,將方法簽名改成了 public static <T> T[] newInstance(Class<T> componentType,int length),這就保證程式通過該 newInstance() 方法建立陣列時的返回值就是陣列物件,而不是 Object 物件,從而避免了強制型別轉換。

提示:程式在①行程式碼處將會有一個 unchecked 編譯警告,所以程式使用了 @SuppressWarnings 來抑制這個警告資訊。

使用反射來獲取泛型資訊

通過指定類對應的 Class 物件,可以獲得該類裡包含的所有成員變數,不管該成員變數是使用 private 修飾,還是使用 public 修飾。獲得了成員變數對應的 Field 物件後,就可以很容易地獲得該成員變數的資料型別,即使用如下程式碼即可獲得指定成員變數的型別。

// 獲取成員變數 f 的型別
Class<?> a = f.getType();

但這種方式只對普通型別的成員變數有效。如果該成員變數的型別是有泛型型別的型別,如 Map<String,Integer> 型別,則不能誰確地得到該成員變數的泛型引數。

為了獲得指定成員變數的泛型型別,應先使用如下方法來獲取該成員變數的泛型型別。

// 獲得成員變數 f 的泛型型別
Type gType = f.getGenericType();

然後將 Type 物件強制型別轉換為 ParameterizedType 物件,ParameterizedType 代表被引數化的型別,也就是增加了泛型限制的型別。ParameterizedType 類提供瞭如下兩個方法。

  • getRawType():返回沒有泛型資訊的原始型別。
  • getActualTypeArguments():返回泛型引數的型別。

下面是一個獲取泛型型別的完整程式。

public class GenericTest {
  private Map<String,Integer> score;

  public static void main(String[] args) throws Exception {
    Class<GenericTest> clazz = GenericTest.class;
    Field f = clazz.getDeclaredField("score");
    // 直接使用getType()取出的型別只對普通型別的成員變數有效
    Class<?> a = f.getType();
    // 下面將看到僅輸出java.util.Map
    System.out.println("score的型別是:" + a);
    // 獲得成員變數f的泛型型別
    Type gType = f.getGenericType();
    // 如果gType型別是ParameterizedType物件
    if (gType instanceof ParameterizedType) {
      // 強制型別轉換
      ParameterizedType pType = (ParameterizedType) gType;
      // 獲取原始型別
      Type rType = pType.getRawType();
      System.out.println("原始型別是:" + rType);
      // 取得泛型型別的泛型引數
      Type[] tArgs = pType.getActualTypeArguments();
      System.out.println("泛型資訊是:");
      for (int i = 0; i < tArgs.length; i++) {
        System.out.println("第" + i + "個泛型型別是:" + tArgs[i]);
      }
    } else {
      System.out.println("獲取泛型型別出錯!");
    }
  }
}

上面程式中的粗體字程式碼就是取得泛型型別的關鍵程式碼。執行上面程式,將看到如下執行結果:

score的型別是:interface java.util.Map
原始型別是:interface java.util.Map
泛型資訊是:
第0個泛型型別是:class java.lang.String
第1個泛型型別是:class java.lang.Integer

從上面的執行結果可以看出,使用 getType() 方法只能獲取普通型別的成員變數的資料型別:對於增加了泛型的成員變數,應該使用 getGenericType() 方法來取得其型別。

提示:Type 也是 java.lang.reflect 包下的一個介面,該介面代表所有型別的公共高階介面,Class 是 Type 介面的實現類。Type 包括原始型別、引數化型別、陣列型別、型別變數和基本型別等。

以上就是深入分析JAVA 反射和泛型的詳細內容,更多關於JAVA 反射和泛型的資料請關注我們其它相關文章!