背景
為什么JDK5要引入泛型,泛型保證參數類型一致性。什么叫類型一致?
假設有繼承關系,A <- B <- C <- D <- E,
ArrayList<C> list;
list.add(new C());
list.add(new D());
list.add(new B());//compile ERROR
並沒有破壞list的類型一致性,因為list被聲明參數類型時C,最終list中所有引用對象都是按照C的類型取出。
有了泛型特性之后,Java就應該使用帶參數的類型,而不是rawtype。但是為了兼容,仍然支持rawtype使用。
泛型有些看起來奇怪的特性,在假設不能使用rawtype只能使用泛型的情況下就很好理解了。
List<Object> 和List<X>沒有層次關系
Java給定一個具體的類型參數A之后的泛型List<Object>,與給定另一個具體的類型參數X的泛型List<X>之間沒有層次關系,不論Object和X類型的層次關系如何。為什么這樣設計?
假設
ArrayList<String> list1 = get();
ArrayList<Object> list2 = list1;
list2.add(new Object());//reasonable
在拿到list2的代碼中,由於list2被聲明為ArrayList<Object>,因此,可以自然向其中添加Object類型的item,這樣,在拿到list1的代碼中認為list1的item的類型時一致的,都是String,取出item的時候就可能得到實際為Object的item,這就會導致程序異常。這是很常見的錯誤發生場景。因此Java禁止他們之間有層次關系。
缺點:無法實現模板方法,接收多種參數類型的泛型。
void recvGen(ArrayList<Object> list){};
ArrayList<String> param = get();
recvGen(param);//ERROR, 兩者沒有關系,所以不能轉型,也不能強制轉型
為此,Java引入了一種帶有通配符的泛型,<?> <? super A><? extends A>
extends和super和?
void recvGen(ArrayList<? extends Object> list){};
ArrayList<String> param = get();
recvGen(param);//pass
/***********************/
void recvGen(ArrayList<? super String> list){};
ArrayList<String> param = get();
recvGen(param);//pass
- super或者extends可以定義一大類的泛型,作為給出具體類型參數的泛型的父類。
- super或者extends定義的有邊界泛型,根據參數類型的層次覆蓋判定和具體參數類型泛型之間的層次關系。
不要望文生義
super關鍵字,用於類的方法中表示指向父類對象的引用。
在泛型邊界語法<? super X>中指出泛型下界,不表示可以存入X的父類對象,只能存入X及其子類對象,取出的對象一律按照Object。
extends在定義類或接口時表示繼承父類,在泛型邊界語法<? extends X>中指出泛型下界,不表示可以存入具體類的子類對象(實際上不能存入任何指定類型的對象),只表示已經存在其中的所有對象可以按照X類型取出。
假設有繼承關系,A <- B <- C <- D <- E
void f( List<? super C> param){...}
List<? super C> 是List<B>,List<A>,List<C>,List<Object>或者List<? super B>,List<? super A>的父類,對於接受List<? super C>為參數的方法f中,傳遞上述類型時,可以隱式向上轉型,安全。在方法中,取出的item都是Object類型,List<? super C>取出的類型都是Object,不是C或者其他的具體類。
如果需要cast取出的item為具體類型,程序員自己保證存在List中的對象都是可以安全強制轉型的。最好的應用場景是不需要針對具體類型的item進行處理,其次是程序員自己保證所有item可以安全強制轉型。
方法f無法接受List<D>或List<E>,cast強制轉型也無法通過編譯,Java不接受沒有層次關系的兩個類型的強制轉型。
假設方法內
List<? super C> param;
param.add(new B());// ERROR
param.add(new Object());//ERROR
param.add(new D());//right
List<? super C>存入item的限制很大而且看起來有點奇怪:任何C的父類和C類型的對象都不能存入,C的子類可以存入。
PECS(producer extends,Consumer super)規則:extends的泛型是生產者,只能從中讀取
super限制的泛型可以作為消費者,可以從存入對象。——但是這不是super的主要意義。
- 為什么
List<? super C> param取出來的是Object,而不是具體類型?
因為在聲明param的時候只要求這個param是List<? super C>,翻譯成人話就是聲明param是List<B>,List<A>,List<C>,List<Object>或者是List<? super B>,List<? super A>中的一種,不能保證是具體的哪一種,所以編譯器不知道取出的item是哪一種具體類型。如果程序員自己能夠保證傳入的是同一種具體類型的item,那么自行強制轉型,但是編譯器只能簡單地約定從List<? super C>中取出的Object類型。
- 為什么不能向
List<? super C>存入A B Object類型,而可以存入C和D E?
因為存入A B或者Object類型可能會破壞原來List的類型一致性,正是為了避免這個問題才在JDK5引入泛型特性的,因此不能允許。而存入C的子類時,通過隱式轉換為C可以保證不因此破壞List中item類型的一致。
- 既然不能保證取出的item是同一種具體類型,要super有何用?
super的意義在於放大模板方法接受的泛型參數類型,同時提供向這個泛型寫入的合法性。
extends的意義也是在於放大模板方法接受的泛型參數類型,犧牲向泛型寫入的可能性,提供保證讀取出的類型有具體類型的便利性。
extends
extends修飾的泛型是只讀的,並且保證讀出的類型都是具體類型。
List<? extends C> void f(){
List<? extends C> ret = getNewList();
for(int i =0; i<10; i++){
ret.add(new C());//ERROR
ret.add(new D());//ERROR
ret.add(new Object());//ERROR
ret.add(null);
}
List<D> tmp = getNewList();
for(int i =0; i<10; i++){
tmp.add(new C());//ERROR
tmp.add(new D());//pass
tmp.add(new E());//pass
tmp.add(null);//pass
}
ret = tmp;
return ret;
}
一個extends通配的泛型List<? extends C>,不可以向其中添加任何具體類型,除了null。
道理和List<Object>不能接受List<String>向上轉型一樣,add任何具體類型可能破壞泛型原有的類型一致性,禁止。
有沒有使用super比使用extends來限定泛型邊界的更好的場景?
大概就是當上界不需要限制的時候,這時如果使用<? extends Object>,那么就相當於沒有限制。
所以super不是完全可以被extends替代的,多一種選擇更好。
總結
super和extends的意義更多是在於模板方法的定義中,模板方法希望放大參數類型從而可以接受更多中的泛型參數。extends是只讀的,讀出的類型具體。super是讀寫的,但是寫入時只能是更具體的類型,讀取時類型只能泛化為Object。
