Java泛型詳解(透徹)
定義
Java中的泛型在JavaSE5中引入。
所謂泛型,即參數化類型。就是說,類型是以參數的方式傳入泛型類。
例如:
ArrayList<Integer> aaryList = new ArrayList<Integer>();
那麽,類型參數就是Integer。
緣由
為什麽要引入泛型呢,得看在沒有泛型的情況下會存在什麽樣的問題。看下面這個非常常見的例子:
ArrayList a = new ArrayList(); a.add(1); a.add("2"); for(int i=0; i<a.size(); i++) { String strTmp = (String)a.get(i); }
點擊運行,啊哦,ClassCastException!
看出來了吧,問題就是類型不安全,給你一個ArrayList,很難直接獲取內部元素的類型,容易引發錯誤。
泛型設計中的坑
JavaSE5中怎麽引入泛型呢?
原先沒有泛型的時候使用方式是這樣:
ArrayList a = new ArrayList();
如果老的方式直接不支持了,那麽基於Java做得那麽多類庫咋辦,豈不是沒法直接遷移至新版本的jdk?
No, No, No~這樣會損失很多用戶~
算了算了,非泛型和泛型兩種方式並存吧。
那麽泛型怎麽在以前的基礎上加入呢?
想來想去,還是在編譯器保證吧!最簡單!
ok,存在什麽問題呢?看個demo:
class GenericsA<T> { T t = new T(); // Error }
天真的我以為可以這麽搞,然而我卻被告知: Type parameter ‘T‘ cannot be instantiated directly.
好吧,原來這個T只是個占位符而已,類型信息並不會在運行時保存!類型信息會被擦除!
類型擦除
泛型的類型信息會被擦除,因此不要奢想在泛型類內部利用類型信息做任何事情。
至於使用泛型類的地方,編譯器來做類型檢查,但是也不足夠安全。
看個例子:
public static void main(String[] args) { ArrayList arrayList = new ArrayList<String>(); arrayList.add("A"); arrayList.add("C"); arrayList.add(3); String tmp = (String)arrayList.get(2); }
啊哦,又是ClassCastException。
別忘記,ArrayList內部沒有類型信息,在把ArrayList<String>轉為ArrayList非泛型列表之後,連編譯器也無法正常類型檢查了!
類型邊界
在編譯器類型檢查之前,泛型的類型信息會被擦除至某個邊界。
普通泛型class ClassA<T>{}中類型會被擦除至Object。
class A<T> { T item; public A(T item) { this.item = item; } void test() { //此處,item只能調用屬於Obeject類的方法 } }
通過泛型類型上設置邊界,我們可以實現類型的限定:
class Animal{ void talk(){} } class B<T extends Animal> { T item; public A(T item) { this.item = item; } void test() { //此處,item可以調用屬於Animal類的方法 item.talk(); } }
甚至,可以設置多個邊界:
interface Eatable {void eat();} interface Drinkable {void drink();} class B<T extends Dog & Cat> { T item; void test() { //此處,item可以調用屬於Eatable和Drinkable的方法 //不過,前提是,item同時是Eatable和Drinkable //(這裏只能通過多個接口實現的方式,或者一個基類和多個接口的方式,因為你無法使一個類繼承自多個父類) } }
通配符
所謂通配符,作用就是匹配多種類型。
1. 上界通配符: < ? extends B >
意思是,可以匹配類型A及其所有子類,即類型的上界是B,無下界。
舉例子:
class A
{
public void print()
{
System.out.println("A");
}
}
class B extends A
{
@Override
public void print()
{
System.out.println("B");
}
}
class C extends B
{
@Override
public void print()
{
System.out.println("C");
}
}
class MyArrayList extends ArrayList<? extends B>
{
}
那麽MyArrayList可以被賦值時:
MyArrayList myArrayList = new ArrayList<A>(); //ERROR MyArrayList myArrayList = new ArrayList<B>(); //OK MyArrayList myArrayList = new ArrayList<C>(); //OK
再來看下元素讀寫情況。
編譯器內心獨白:
- >>>> 既然設了上界通配符,那麽我MyArrayList中的元素實際類型可能為B和B的任何子類,那麽為了確保類型安全,只有同時繼承自B和所有B的子類的類型才能add。
- >>>> 嗯?好像沒有任何一種類型可用滿足以上條件。
- >>>> 好吧,上界通配符泛型中,myArrayList不接受add方法。
- >>>> 哦,對了,任何以泛型類型為參數的方法我都不接受!
好吧,經歷了編譯器的內心一番搏鬥,所有以泛型類型為參數的方法都沒法使用了:
myArrayList.add(new B()); //ERROR
myArrayList.add(new C()); //ERROR
myArrayList.set(0, new B()); //ERROR
但是,get()、remove()方法等方法還是可以用的。
2. 下界通配符 ? super B
意思是,可以匹配類型B及其所有基類,即類型的下界是B,上界是Object。
接著舉例子:
class MyArrayList extends ArrayList<? super B>
{}
那麽:
MyArrayList myArrayList = new ArrayList<A>(); //OK MyArrayList myArrayList = new ArrayList<B>(); //OK MyArrayList myArrayList = new ArrayList<C>(); //ERROR MyArrayList myArrayList = new ArrayList<Object>(); //OK
上至Object,下至B,所有的實例都可以賦值給泛型為<?super B>的myArrayList。
再次看下元素讀寫情況。
編譯器:
- >>>> 既然設了下界通配符,那麽我MyArrayList中的元素實際類型可能為B和B的任何父類,那麽為了確保類型安全,只有同時是B以及B的所有父類的類型實例才能add。
- >>>> 想一下,哪些類型符合條件呢?恩,好像所有B和B子類的實例都滿足條件。例如。new B()既是B又是Object,new C()一樣可以向上轉型至B和Obejct....
- >>>> 當然,所有B及其子類的實例都可以add進去。
- >>>> 那麽,get方法?
- >>>> myArrayList內部可能的類型那麽多(B及其所有父類),get的返回值類型咋辦呢,算了,用通用的Object吧,真實類型讓那些碼農自己判斷去吧!
Java泛型詳解(透徹)