對泛型與反射的一些研究和看法

來源:互聯網
上載者:User

研究泛型與反射之間的關係非常有趣。
我們知道,反射和泛型都是Java的一種動態技術。而不像繼承和多態,是物件導向的技術。可以這麼說,反射和泛型都像是為了彌補像繼承和多態這些物件導向技術的不足而產生的。模式多是建立在物件導向技術基礎上的,因而每種模式多多少少在動態性,或者說擴充性方面有些不足,我們就又結合了反射和泛型對模式進行一定的擴充,使它在動態性方面更符合我們的要求。
在將這些技術結合起來的過程中,我們多多少少會想到將泛型和反射結合起來。這是非常有趣的話題:範型和反射都使得Java有了一定的擴充性,但同時都有它自己的不足,而將這兩種技術結合起來,是不是又能解決各自的不足,使得它們在動態性上更上一層樓呢?
正像前面所說的,泛型和反射可以相互促進,我們先來看看泛型是怎麼協助反射的。
我們知道,使用反射的一個最大的煩惱就是應用反射以後得到的基本上都是Object類型的對象,這種對象要使用,需要我們進行強制類型轉化。而我們更知道,泛型的功能之一就是消除我們使用強制類型轉化。
1.  運行期內初始化對象
運行期內初始化對象是我們最常用的反射功能,但是我們通過反射在運行期內得到的對象的類型通常是Object類型的,而這個對象又需要我們在使用的時候進行強制類型轉化。現在,有了反射,可以使我們不需要做強制類型轉化這個工作。
假設我們已經有了兩個類:
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)

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.