首頁 > Java > java教程 > 泛型與反射的使用總結之泛型篇

泛型與反射的使用總結之泛型篇

高洛峰
發布: 2016-12-19 15:39:00
原創
1626 人瀏覽過

     在我看來,JDK5.0絕對是一個很具有里程碑意義的版本,在這個版本中,提供了非常多的很有價值的新特性,泛型就是其中之一,並且對反射機制進行了增強,而且5.0版本也把以前集合框架進行了重構全部添加了泛型支援。
    從5.0發佈到現在差不多快有10年時間了,關於這方面的知識介紹網上可以查到很多,書上也都有講到。大象現在再寫這些東西,一是將自己的經歷體會總結出來作一個積累,另外一點是希望能夠給剛接觸這方面的童鞋一點幫助。
    泛型最大的好處是類型檢查,尤其是對集合非常有用,另外在底層程式碼設計中很有用處,它實現了重複使用的功能。泛型有兩種定義方式,一個是泛型類,另一個是泛型方法。
    那到底什麼是泛型呢?簡單點講(可能不嚴謹),就是用到了類型參數這樣的類型變量,不管是類別、介面還是方法,都可以說是用到了泛型。請看範例:
    泛型類別
    public class Person {
        private T   t;
        }
        public void setT(T t) {
          
        }
    }
    T是型態變量,而為參數化型,以尖括號()括起來,放在類別名稱的後面。泛型類別的類型變數可以定義多個。類型變數一般都使用一個大寫字母表示,例如本例的Person,JDK中的List,Map等等。
    用特定的類別替換類型變數就可以實例化泛型類別:Person person = new Person();
    像這樣實例化是錯誤的:Person person = new Person (); //ERROR
    泛型方法


public  T get(String key, Object params) {

       return  這是我在SSM3範例的MyBatisDao這個類別裡面定義的一個方法,此方法就是一個泛型方法。 就是型別變量,而get前面的T是回傳類型。其實這個方法是存在型別安全問題的,如果我在RoleService裡面呼叫這個方法,將回傳類型T寫成User,編譯器是不會有任何警告訊息的。

    但如果我改寫一下,將MyBatisDao加上泛型,public class MyBatisDao extends SqlSessionDaoSupport
    這時🜎

    編譯器根據MyBatisDao這個Role類型變數就會推斷它裡面定義的get方法應該回傳Role類型。不過這樣改過之後MyBatisDao就變成泛型類別了,而get方法也不再是泛型方法。那麼泛型方法能不能有安全檢查呢?有,但是需要一些程式設計技巧,關鍵還是跟你寫的泛型方法有關係,後面提到的類型參數的限定可以對泛型加以約束,解決一些安全檢查的問題。
    類型參數的限定
    對於像這樣的型別變數所代表的範圍有時太大了點,有時不方便使用。例如現在需要實作了java.io.Serializable介面的泛型類,那麼這應該如何做呢? JDK那幫專家們為我們設計了一種叫做「有限制的通配符類型」來解決這個問題。一般我們稱為上限和下限,他們一般寫成下面這樣:
    上限:或 extends T>
    下限: 任何類型。有時候使用類型變數不是那麼的方便,通配符類型就很好的解決了這個問題。
    的意思是,T為實現了Serializable介面的類,T為綁定型別(Serializable)的子型,T和綁定型別可以是介面也可以是類。如果想再加個實作了Comparable介面的限定,只需要這樣寫:這樣寫有點不嚴謹,因為Comparable介面是個泛型介面可以接收泛型參數,現在我們不討論這麼複雜的情況。
     super T>可以這樣理解,任何T類型變數的超類型,也包括T本身,因為T可以看成是它本身的一個超類型。
    那為什麼說extends是上限,而extends是下限呢?透過前面兩個解釋就應該可以看出來,extends Serializable或是extends T說明類型變數必須是Serializable的子類別和T變數的子類型,這是不是相當於限制了類型變數的上限了?同理就可以理解下限的意思了。
    說了這麼多關於上限和下限的東西,那他們到底有什麼用?和怎麼用呢?簡單來講,extends限定的型別參數可以從泛型物件讀取,super限定的型別參數可以寫入泛型物件。這樣說可能有些童鞋要暈了,這到底說的神馬東西呢?
    讓我們換個方式來講,關於泛型的上限與下限已經總結出來一個公式:PECS
    PECS表示producer-extends,consumer-super
    上面這個就是說如果參數化類型表示一個生產者,就用,如果它表示一個消費者,就用 super T>。再結合上面的說明一起去理解,是不是清楚了呢?如果還不是很理解,大象再附上一小段程式碼來體會下其中的差異。
    public void add(List extends T> list){
        for(T t : list){
          public void add(T t){};
    public void add(T t, List super T> list){
        list.add(t);
    }
    泛型的一般化
  擦除現在寫代碼用Eclipse或IntelliJ,這些整合開發工具都能做到即時編譯,哪裡有錯馬上會出現紅色的錯誤標識。因此如果出現類型轉換錯誤,會很明顯的看到結果。但是,在程式的運行階段,JVM是不認識泛型是神馬東西的,所有有泛型的類,接口,方法都會被擦除掉泛型,變成原生類型(raw type),即Person變成Person,List extends T> list變成List list等等。
    下面就是先前的Person類別用javap反編譯後的結果,所有的型別變數T都被擦除掉了,因為T是無限定型別所以用Object取代。而且add方法中的 extends T>和 super T>也被去掉了。
    public class com.bolo.Person extends java.lang.Object{
       private java.lang.Object t;
   
public void setT(java.lang.Object);
       public void add(java.util.List);
       public void add(java.lang.Object); List);
    }
    因此針對泛型擦除此特點,我們需要注意這樣幾點:
        1、JVM裡面沒有泛型,只有普通的類別與方法。
        2、所有的型別參數都以限定型別或無限定型別用Object來取代。
        3、請謹慎處理方法重載,錯誤的使用重載不會達到想要的多態。
        4、為保固類型安全,必要時請使用強制型別轉換。
    泛型的限制
    基本型別不能作為型別參數。 Person就是錯誤的,只能用Person
    類型檢查只能用原始型別。 if(t instanceof Person) 若寫成if(t instanceof Person)馬上會出現編譯錯誤
    則無法實例化型別變數。這樣寫是錯誤的:T t = new T()
    不能實例化參數化類型的陣列。 Person[] p = new Person[5] //ERROR
    無法定義靜態實例變數與靜態方法。如果你想這樣寫:private static T a 那對不起,編譯器馬上會給你一個錯誤提示。
    其實關於泛型的限製完全可以不用講,現在編譯器都很強大,只要你這樣做了,馬上會給你顯示一個錯誤。
    最後說下泛型對於集合的用處來說是最大的,集合是一個容器,有了泛型就更方便重用。而我們使用最頻繁的集合就是List列表,還有一個容器就是數組,大像在這裡強烈建議大家多用List,盡量或最好不要用數組。其一是List有型別安全性檢查,其二是數組的功能List都提供了並且更豐富,其三List對gc進行了最佳化。如果使用數組,特別是操作物件數組,如果經驗不足,沒有釋放數組裡面的物件引用,則很容易造成記憶體洩漏的問題。




更多泛型與反射的使用總結之泛型篇相關文章請關注PHP中文網!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板