天天看點

java學習筆記-java泛型

大象根據自己對泛型和反射的使用,來談談對它們的了解,順便整理一下知識,記錄下來,以便以後查找。

至少在我看來,JDK5.0絕對是一個很具有裡程碑意義的版本,在這個版本中,提供了非常多的很有價值的新特性,泛型就是其中之一,并且對反射機制進行了增強,而且5.0版本還把以前集合架構進行了重構全部添加了泛型支援。

從5.0釋出到現在差不多快有10年時間了,關于這方面的知識介紹網上可以查到很多,書上也都有講到。大象現在再寫這些東西,一是将自己的經曆體會總結出來作一個積累,另外一點是希望能夠給剛接觸這方面的童鞋一點幫助。

泛型最大的好處就是類型檢查,尤其是對集合非常有用,另外在底層代碼設計中很有用處,它實作了重用的功能。泛型有兩種定義方式,一個是泛型類,另一個是泛型方法。

那到底什麼是泛型呢?簡單點講(可能不嚴謹),就是用到了類型參數這樣的類型變量,不管是類、接口還是方法,都可以說是用到了泛型。請看例子:

**泛型類**
           
public class Person<T> {
        private T t;
        public T getT() {
            return t;
        }
        public void setT(T t) {
            this.t = t;
        }
    }
           
T就是類型變量,是一個參數化類型,用尖括号(<>)括起來,放在類名的後面。泛型類的類型變量可以定義多個。類型變量一般都使用一個大寫字母表示,比如本例的Person<T>,JDK中的List<E>,Map<K,V>等等。
用具體的類替換類型變量就可以執行個體化泛型類:Person<Man> person = new Person<Man>();
像這樣執行個體化是錯誤的:Person<T> person = new Person<T>(); //ERROR
           

泛型方法

public <T> T get(String key, Object params) {
       return (T) getSqlSession().selectOne(key, params);
    }
           

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

但如果我改寫一下,将MyBatisDao加上泛型,public class MyBatisDao extends SqlSessionDaoSupport

這時User的傳回類型就出現編譯錯誤了:

java學習筆記-java泛型

編譯器根據MyBatisDao這個Role類型變量就會推斷出它裡面定義的get方法應該傳回Role類型。不過這樣改過之後MyBatisDao就變成泛型類了,而get方法也不再是泛型方法。那麼泛型方法能不能有安全檢查呢?有,但是需要一些程式設計技巧,關鍵還是跟你寫的泛型方法有關系,後面提到的類型參數的限定可以對泛型加以限制,解決一些安全檢查的問題。

類型參數的限定

對于像這樣的類型變量所代表的範圍有時太大了點,有時不友善使用。比如現在需要實作了java.io.Serializable接口的泛型類,那麼這應該如何做呢?JDK那幫專家們為我們設計了一種叫做“有限制的通配符類型”來解決這個問題。一般我們稱為上限 和下限,他們一般寫成下面這樣:

上限:<T extends Serializable>或者<? extends T>
    下限:<? super T>
    問号(?)叫做無限制的通配符,它可以表示任何類型。有時候使用類型變量不是那麼的友善,通配符類型就很好的解決了這個問題。
    <T extends Serializable>的含義是,T為實作了Serializable接口的類,T為綁定類型(Serializable)的子類型,T和綁定類型可以是接口也可以是類。如果想再加個實作了Comparable接口的限定,隻需要這樣寫:<T extends Serializable & Comparable>這樣寫有點不嚴謹,因為Comparable接口是一個泛型接口可以接收泛型參數,現在我們不讨論這麼複雜的情況。
    <? super T>可以這樣了解,任何T類型變量的超類型,還包括T本身,因為T可以看成是它本身的一個超類型。
    那為什麼說extends是上限,而extends是下限呢?通過前面兩個解釋就應該可以看出來,extends Serializable或者extends T說明類型變量必須是Serializable的子類和T變量的子類型,這是不是相當于限制了類型變量的上限了?同理就可以了解下限的含義了。
           

說了這麼多關于上限和下限的東西,那他們到底有什麼用?和怎麼用呢?簡單來講,extends限定的類型參數可以從泛型對象讀取,super限定的類型參數可以向泛型對象寫入。這樣說可能有些童鞋要暈了,這到底說的神馬東西呢?

讓我們換個方式來講,關于泛型的上限與下限已經總結出來一個公式:PECS

PECS表示producer-extends,consumer-super

public void add(List<? extends T> list){
        for(T t : list){
           add(t);
       }
    }
    public void add(T t){};
    public void add(T t, List<? super T> list){
        list.add(t);
    }
           

泛型的擦除

泛型主要是在編譯期有效,即編譯的時候檢查類型安全,現在寫代碼一般都會用Eclipse或IntelliJ,這些內建開發工具都能做到即時編譯,哪裡有錯馬上會出現紅色的錯誤辨別。是以如果出現類型轉換錯誤,會很明顯的看到結果。但是,在程式的運作階段,JVM是不認識泛型是神馬東西的,所有有泛型的類,接口,方法都會被擦除掉泛型,變成原生類型(raw type),即Person<T>變為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 com.bolo.Person();
       public java.lang.Object getT();
       public void setT(java.lang.Object);
       public void add(java.util.List);
       public void add(java.lang.Object);
       public void add(java.lang.Object, java.util.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進行了優化。如果使用數組,特别是操作對象數組,如果經驗不足,沒有釋放數組裡面的對象引用,則很容易造成記憶體洩漏的問題。

作者:大象

出處:http://www.blogjava.net/bolo/archive/2014/04/29/413060.html