1. gzyueqian
      18529173453
      首頁 > 新聞中心 > > 正文

      泛型與反射

      更新時間: 2007-05-09 13:25:02來源: 粵嵌教育瀏覽量:1002


        摘要:反射和泛型的功能都十分強大,將它們結合起來也特別有趣,本文試圖就Java語言的反射和泛型的關系做一點探討,用以拋磚引玉,引起大家對兩種技術的興趣!

        研究泛型與反射之間的關系非常有趣。

        我們知道,反射和泛型都是Java的一種動態技術。而不像繼承和多態,是面向對象的技術??梢赃@么說,反射和泛型都像是為了彌補像繼承和多態這些面向對象技術的不足而產生的。模式多是建立在面向對象技術基礎上的,因而每種模式多多少少在動態性,或者說擴展性方面有些不足,我們就又結合了反射和泛型對模式進行一定的擴展,使它在動態性方面更符合我們的要求。

        在將這些技術結合起來的過程中,我們多多少少會想到將泛型和反射結合起來。這是非常有趣的話題:范型和反射都使得Java有了一定的擴展性,但同時都有它自己的不足,而將這兩種技術結合起來,是不是又能解決各自的不足,使得它們在動態性上更上一層樓呢?

        正像前面所說的,泛型和反射可以相互促進,我們先來看看泛型是怎么幫助反射的。
        我們知道,使用反射的一個的煩惱就是應用反射以后得到的基本上都是Object類型的對象,這種對象要使用,需要我們進行強制類型轉化。而我們更知道,泛型的功能之一就是消除我們使用強制類型轉化。

      1. 運行期內初始化對象

        運行期內初始化對象是我們常用的反射功能,但是我們通過反射在運行期內得到的對象的類型通常是Object類型的,而這個對象又需要我們在使用的時候進行強制類型轉化?,F在,有了反射,可以使我們不需要做強制類型轉化這個工作。
        假設我們已經有了兩個類:
      public class Cls1 {

      public void do1() {
      // TODO Auto-generated method stub
      System.out.println("cls1...");

      }

      }
      public class Cls2 {

      public void do2() {
      // TODO Auto-generated method stub
      System.out.println("cls2...");
      }

      }

        我們需要在運行期內初始化這兩個對象,我們可以設計如下的初始化方法:

      public class Factory{
      public static <U extends Object>U getInstance(String clsName)
      {
      try
      {
      Class<?> cls = Class.forName(clsName);
      return (U) cls.newInstance();
      }
      catch(Exception e)
      {
      e.printStackTrace();
      return null;
      }
      }
      }

        在這個方法里,我們其實是利用泛型在初始化方法里提前做了強制類型轉化的工作,這樣使得我們不必在使用的時候進行強制類型轉化的工作。

      它們的測試代碼如下:
      Cls1 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");
      i1.do1();
      Cls2 i2 = Factory.getInstance("fanxing.factory.dynaFactory.Cls2");
      i2.do2()
      測試結果為:
      cls1...
      cls2...

      需要注意的是,使用這種方法有幾個問題:
        , return (U) cls.newInstance();這個語句會有警告性的錯誤,好在這種錯誤不是編譯性的錯誤,還是我們可以承受的。
        第二, 編譯器不能做類型檢查,如Cls1 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");這句,換成Cls2 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");也是可以編譯過去的。只是在運行的時候才會出錯。

        除了上面的方法,還有一種更好的方法。
        這個方法需要我們在運行期內傳入的對象是Class對象,當然是經過泛型的Class對象,就像下面的樣子:

      Class<Cls1> cls = Cls1.class;
      try
      {
      Intf1 obj = cls.newInstance();
      obj.do1();
      }
      catch(Exception e)
      {
      e.printStackTrace();
      }

        可以很清楚地看到,這里完全沒有了強制類型轉化,當然也就沒有種方法所出現的那兩個問題。
      運行結果為:

      cls1...

      根據這個方法,我們可以把前面的初始化方法改造為:
      public static <U extends Object> U getInstance(Class<U> cls)
      {
      try
      {
      return cls.newInstance();
      }
      catch(Exception e)
      {
      e.printStackTrace();
      return null;
      }
      }

      我們來進行以下測試:
      Cls1 c1 = Factory.getInstance(Cls1.class);
      c1.do1();

      測試結果為:

      cls1...

      這時候,如果我們將上面的測試代碼改為:

      Cls2 c1 = Factory.getInstance(Cls1.class);

        就會出現編譯錯誤,可以看到,我們的第二種方法的確避免了種方法的弱點,但種方法的好處是,只需要往初始化方法里輸入類名作為參數。

      2. 運行期內調用方法

        使用過反射的人都知道,運行期內調用方法后得到的結果也將是一個Object對象。這樣的結果在一般的方法反射上也就是可以忍受的了。但是有時候也是不能忍受的,比如,我們想反射List<T>類的iterator()方法。
      一般的,我們使用反射可以這么做:

      try
      {
      Class<?> cls = Class.forName("java.util.ArrayList");
      Method m = cls.getDeclaredMethod("iterator",new Class[0]);
      return m.invoke(this,new Object[0]);
      }
      catch(Exception e)
      {
      e.printStackTrace();
      return null;
      }

        當然,這里返回的是一個Object對象,而List<T>類的iterator()的實際返回類型為T。很明顯,我們可以將上面的代碼做如下的修改:

      try
      {
      Class<?> cls = Class.forName("java.util.ArrayList");
      Method m = cls.getDeclaredMethod("iterator",new Class[0]);
      return (T)m.invoke(this,new Object[0]);
      }
      catch(Exception e)
      {
      e.printStackTrace();
      return null;
      }

        同樣,我們的代碼也會遇到警告性的錯誤。但是我們可以置之不理。

      3. 運行期內初始化數組對象

        同樣,在運行期內初始化得到的數組也是一個Object對象。就像下面的樣子:
      Object o = Array.newInstance(int.class,10);

        如果我想在getArray()方法里得到數組對象,就會得到像下面這個樣子的代碼:
      public Object getArray(Class cls,int size)
      {
      return Array.newInstance(cls,size);
      }

        這顯然不會令我們滿意。因為如果我們輸入一個Class<T>類型的參數,就希望返回一個T類型的結果。

      在這種想法下,我們就使用泛型將getArray()方法做如下修改:
      public T[] getArray(Class<T> cls,int size)
      {
      return (T[])Array.newInstance(cls,size);
      }

        這樣的修改,將使我們得到一個比較滿意的結果。同樣,上面的代碼也會得到一個警告性的錯誤。但是,使用泛型使我們的結果得到了很大的優化。

        上面的幾個例子讓我們看到了泛型能夠幫助反射,而且是大大優化了反射的結果。但在同時,反射也不是被動的接受泛型的幫助,反射同樣也可以幫助泛型。這是基于反射的基本工作原理:得到數據的元數據。也就是說,可以通過反射得到泛型的元數據。除此之外,反射也有利于幫助泛型數據運行期內初始化。

      下面以一兩個簡單例子加以說明。

      4. 使用反射初始化泛型類

        我們通常會在一個類里這樣使用泛型:
      public final class Pair<A,B> {
      public final A fst;
      public final B snd;

      public Pair(A fst, B snd) {
      this.fst = fst;
      this.snd = snd;
      }
      ……
      }

        這當然是我們基本的用法,但常常會這樣:編譯器希望知道更多的關于這個未知對象如A fst的信息,這樣,我們可以在運行期內調用某一些方法。大家說啊,這很容易啊,我們可以把這種未知類型作為參數輸入。呵呵,這就對了,有了這樣參數,下一步,我們就要使用反射在運行期內調用它的方法。

        關于這樣的例子,我在這里不再給出。我在這里給出一個簡單一些的例子,就是對泛型類初始化需要調用的構造器。
      對于上面的Pair<A,B>類,如果構造器的輸入參數的類型不是A和B,而是Class<A>和Class<B>,那么我們就不得不在構造器里使用反射了。

      public Pair(Class<A> typeA, Class<B> typeB) {
      this.fst = typeA.newInstance();
      this.snd = typeB.newInstance();
      ……
      }

        由此可見,對于泛型里的未知類型參數,我們也完全可以和普通類型一樣使用反射工具。即可以通過反射對這些未知類型參數做反射所能做到的任何事情。

      5. 使用反射得到泛型信息

        關于這一個小結的問題,顯得更加得有趣。
        我們還是以上面的Pair<A,B>作為例子,假如我們在一個類中使用到了這個類,如下所示:

      public Class PairUser
      {
      private Pair<String,List> pair;
      ……
      }

        如果我們對PairUser類應用反射,我們可以很輕松的得到該類的屬性pair的一些信息,如它是private還是public的,它的類型等等。

        如果我們通過反射得到pair屬性的類型為Pair以后,我們知道該類是一個泛型類,那么我們就想進一步知道該泛型類的更進一步的信息。比如,泛型類的類名,泛型參數的信息等等。

        具體到上面的PairUser類的例子,我現在想知道它的屬性pair的一些信息,比如,它的類型名、泛型參數的類型,如String和List等等這些信息。所要做的工作如下:

      首先是取得這個屬性
      Field field = PairUser.class.getDeclaredField("pair");
      然后是取得屬性的泛型類型
      Type gType = field.getGenericType();
      再判斷gType是否為ParameterizedType類型,如果是則轉化為ParameterizedType類型的變量
      ParameterizedType pType = (ParameterizedType)gType;
      取得原始類型
      Type rType = pType.getRawType();
      然后就可以通過rType.getClass().getName()獲得屬性pair的類型名。
      獲取參數信息
      Type[] tArgs = pType.getActualTypeArguments();
      可以通過tArgs[j].getClass().getName()取得屬性pair的泛型參數的類型名。

      完整的代碼如下:
      try
      {
      Field field = PairUser.class.getDeclaredField("pair");
      Type gType = field.getGenericType();
      if(gType instanceof ParameterizedType)
      {
      ParameterizedType pType = (ParameterizedType)gType;
      Type rType = pType.getRawType();
      System.out.println("rawType is instance of " +
      rType.getClass().getName());
      System.out.println(" (" + rType + ")");
      Type[] tArgs = pType.getActualTypeArguments();
      System.out.println("actual type arguments are:");
      for (int j = 0; j < tArgs.length; j++) {
      System.out.println(" instance of " +
      tArgs[j].getClass().getName() + ":");
      System.out.println(" (" + tArgs[j] + ")");
      }
      }
      else
      {
      System.out.println("getGenericType is not a ParameterizedType!");
      }
      }
      catch(Exception e)
      {
      e.printStackTrace();
      }

      }

      輸出結果為:
      rawType is instance of java.lang.Class
      (class Pair)
      actual type arguments are:
      instance of java.lang.Class:
      (class java.lang.String)
      instance of java.lang.Class:
      (interface java.util.List)

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 亚洲精选品质AV在线 | 久久久特色aⅴ片免费观看 午夜在线亚洲免费 | 亚洲国产精品一区二区第一页 | 亚洲最新中文字幕aⅴ天堂 香蕉久久高清免费 | 中文精品欧美无线码一区 | 亚州最新精品一区二区三区 |