大象根據自己對泛型和反射的使用,來談談對它們的理解,順便整理一下知識,記錄下來,以便以后查找。
至少在我看來,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這個類里面定義的一個方法,此方法就是一個泛型方法。<T>就是類型變量,而get前面的T是返回類型。其實這個方法是存在類型安全問題的,如果我在RoleService里面調用這個方法,將返回類型T寫成User,編譯器是不會有任何警告信息的。 但如果我改寫一下,將MyBatisDao加上泛型,public class
MyBatisDao<T> extends
SqlSessionDaoSupport 這時User的返回類型就出現編譯錯誤了:

編譯器根據MyBatisDao<Role>這個Role類型變量就會推斷出它里面定義的get方法應該返回Role類型。不過這樣改過之后MyBatisDao就變成泛型類了,而get方法也不再是泛型方法。那么泛型方法能不能有安全檢查呢?有,但是需要一些編程技巧,關鍵還是跟你寫的泛型方法有關系,后面提到的類型參數的限定可以對泛型加以約束,解決一些安全檢查的問題。
類型參數的限定 對于像<T>這樣的類型變量所代表的范圍有時太大了點,有時不方便使用。比如現在需要實現了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 上面這個就是說如果參數化類型表示一個生產者,就用<? extends
T>,如果它表示一個消費者,就用<?
super T>。再結合上面的說明一起來理解,是不是清楚了呢?如果還不是很理解,大象再附上一小段代碼來體會下其中的區別。
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<int>就是錯誤的,只能用Person<Integer> 類型檢查只能用原始類型。if(t instanceof
Person) 如果寫成if(t instanceof
Person<String>)馬上會出現編譯錯誤
不能實例化類型變量。這樣寫是錯誤的:T t
= new T() 不能實例化參數化類型的數組。Person<Integer>[] p = new
Person<Integer>[5] //ERROR 不能定義靜態實例變量和靜態方法。如果你想這樣寫:private static T
a 那對不起,編譯器馬上會給你一個錯誤提示。 其實關于泛型的限制完全可以不用講,現在編譯器都很強大,只要你這樣做了,馬上會給你顯示一個錯誤。 最后說下泛型對于集合的用處來說是最大的,集合是一個容器,有了泛型就更方便重用。而我們使用最頻繁的集合就是List列表,還有一個容器就是數組,大象在這里強烈建議大家多用List,盡量或最好不要用數組。其一是List有類型安全性檢查,其二是數組的功能List都提供了并且更豐富,其三List對gc進行了優化。如果使用數組,特別是操作對象數組,如果經驗不足,沒有釋放數組里面的對象引用,則很容易造成內存泄漏的問題。
以上這些都是大象使用泛型的一點經驗總結,有什么不對的,或不完善的地方,還請各位指出來,謝謝! 本文為菠蘿大象原創,如要轉載請注明出處。http://www.tkk7.com/bolo
posted on 2014-04-29 17:09
菠蘿大象 閱讀(7028)
評論(0) 編輯 收藏 所屬分類:
Java