您好,登錄后才能下訂單哦!
《Java編程思想》第四版足足用了75頁(yè)來(lái)講泛型——厚厚的一沓內(nèi)容,很容易讓人頭大——但其實(shí)根本不用這么多,只需要一句話:我是一個(gè)泛型隊(duì)列,狗可以站進(jìn)來(lái),貓也可以站進(jìn)來(lái),但最好不要既站貓,又站狗!
泛型,有人拆解這個(gè)詞為“參數(shù)化類(lèi)型”。這種拆解其實(shí)也不好理解,還是按照沉默王二的意思來(lái)理解一下吧。
現(xiàn)在有一只玻璃杯,你可以讓它盛一杯白開(kāi)水,也可以盛一杯二鍋頭——泛型的概念就在于此,制造這只杯子的時(shí)候沒(méi)必要在說(shuō)明書(shū)上定義死,指明它只能盛白開(kāi)水而不能盛二鍋頭!
可以在說(shuō)明書(shū)上指明它用來(lái)盛裝液體,但最好也不要這樣,弄不好用戶想用它來(lái)盛幾塊冰糖呢!
這么一說(shuō),你是不是感覺(jué)不那么抽象了?泛型其實(shí)就是在定義類(lèi)、接口、方法的時(shí)候不局限地指定某一種特定類(lèi)型,而讓類(lèi)、接口、方法的調(diào)用者來(lái)決定具體使用哪一種類(lèi)型的參數(shù)。
就好比,玻璃杯的制造者說(shuō),我不知道使用者用這只玻璃杯來(lái)干嘛,所以我只負(fù)責(zé)造這么一只杯子;玻璃杯的使用者說(shuō),這就對(duì)了,我來(lái)決定這只玻璃杯是盛白開(kāi)水還是二鍋頭,或者冰糖。
我們來(lái)看一段簡(jiǎn)短的代碼:
public class Cmower {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
Cmower cmower = new Cmower();
Map map = new HashMap();
map.put("dog", cmower.new Dog());
map.put("cat", cmower.new Cat());
Cat cat = (Cat) map.get("dog");
System.out.println(cat);
}
}
這段代碼的意思是:我們?cè)趍ap中放了一只狗(Dog),又放了一只貓(Cat),當(dāng)我們想從map中取出貓的時(shí)候,卻一不留神把狗取了出來(lái)。
這段代碼編譯是沒(méi)有問(wèn)題的,但運(yùn)行的時(shí)候就會(huì)報(bào)ClassCastException
(狗畢竟不是貓啊):
Exception in thread "main" java.lang.ClassCastException: com.cmower.java_demo.sixteen.Cmower$Dog cannot be cast to com.cmower.java_demo.sixteen.Cmower$Cat
at com.cmower.java_demo.sixteen.Cmower.main(Cmower.java:20)
為什么會(huì)這樣呢?
1)寫(xiě)代碼的程序員粗心大意。要從map中把貓取出來(lái),你不能取狗啊!
2)創(chuàng)建map的時(shí)候,沒(méi)有明確指定map中要放的類(lèi)型。如果指定是要放貓,那肯定取的時(shí)候就是貓,不會(huì)取出來(lái)狗;如果指定是要放狗,也一個(gè)道理。
第一種情況不太好解決,總不能把程序員打一頓(我可不想做一個(gè)天天背鍋的程序員,很重的好不好);第二種情況就比較容易解決,因?yàn)镸ap支持泛型(泛型接口)。
public interface Map<K,V> {
}
注:在Java中,經(jīng)常用T、E、K、V等形式的參數(shù)來(lái)表示泛型參數(shù)。
T:代表一般的任何類(lèi)。
E:代表 Element 的意思,或者 Exception 異常的意思。
K:代表 Key 的意思。
V:代表 Value 的意思,通常與 K 一起配合使用。
既然Map支持泛型,那作為Map的實(shí)現(xiàn)者HashMap(泛型類(lèi))也支持泛型了。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
}
其中的put方法(泛型方法)是這樣定義的:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
好了,現(xiàn)在使用泛型的形式來(lái)定義一個(gè)只能放Cat的Map吧!
public class Cmower {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
Cmower cmower = new Cmower();
Map<String, Cat> map = new HashMap<>();
// map.put("dog", cmower.new Dog()); // 不再允許添加
map.put("cat", cmower.new Cat());
Cat cat = map.get("cat");
System.out.println(cat);
}
}
當(dāng)使用泛型定義map(鍵為String類(lèi)型,值為Cat類(lèi)型)后:
1)編譯器就不再允許你向map中添加狗的對(duì)象了。
2)當(dāng)你從map中取出貓的時(shí)候,也不再需要強(qiáng)制轉(zhuǎn)型了。
有人說(shuō),Java的泛型做的只是表面功夫——泛型信息存在于編譯階段(狗隊(duì)在編譯時(shí)不允許站貓),運(yùn)行階段就消失了(運(yùn)行時(shí)的隊(duì)列里沒(méi)有貓的信息,連狗的信息也沒(méi)有)——這種現(xiàn)象被稱(chēng)為“類(lèi)型擦除”。
來(lái),看代碼解釋一下:
public class Cmower {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
Cmower cmower = new Cmower();
Map<String, Cat> map = new HashMap<>();
Map<String, Dog> map1 = new HashMap<>();
// The method put(String, Cmower.Cat) in the type Map<String,Cmower.Cat> is not applicable for the arguments (String, Cmower.Dog)
//map.put("dog",cmower.new Dog());
System.out.println(map.getClass());
// 輸出:class java.util.HashMap
System.out.println(map1.getClass());
// 輸出:class java.util.HashMap
}
}
map的鍵位上是Cat,所以不允許put一只Dog;否則編譯器會(huì)提醒The method put(String, Cmower.Cat) in the type Map<String,Cmower.Cat> is not applicable for the arguments (String, Cmower.Dog)
。編譯器做得不錯(cuò),值得點(diǎn)贊。
但是問(wèn)題就來(lái)了,map的Class類(lèi)型為HashMap,map1的Class類(lèi)型也為HashMap——也就是說(shuō),Java代碼在運(yùn)行的時(shí)候并不知道m(xù)ap的鍵位上放的是Cat,map1的鍵位上放的是Dog。
那么,試著想一些可怕的事情:既然運(yùn)行時(shí)泛型的信息被擦除了,而反射機(jī)制是在運(yùn)行時(shí)確定類(lèi)型信息的,那么利用反射機(jī)制,是不是就能夠在鍵位為Cat的Map上放一只Dog呢?
我們不妨來(lái)試一試:
public class Cmower {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
Cmower cmower = new Cmower();
Map<String, Cat> map = new HashMap<>();
try {
Method method = map.getClass().getDeclaredMethod("put",Object.class, Object.class);
method.invoke(map,"dog", cmower.new Dog());
System.out.println(map);
// {dog=com.cmower.java_demo.sixteen.Cmower$Dog@55f96302}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
看到?jīng)]?我們竟然在鍵位為Cat的Map上放了一只Dog!
注:Java的設(shè)計(jì)者在JDK 1.5時(shí)才引入了泛型,但為了照顧以前設(shè)計(jì)上的缺陷,同時(shí)兼容非泛型的代碼,不得不做出了一個(gè)折中的策略:編譯時(shí)對(duì)泛型要求嚴(yán)格,運(yùn)行時(shí)卻把泛型擦除了——要兼容以前的版本,還要升級(jí)擴(kuò)展新的功能,真的很不容易!
有些時(shí)候,你會(huì)見(jiàn)到這樣一些代碼:
List<? extends Number> list = new ArrayList<>();
List<? super Number> list = new ArrayList<>();
?
和關(guān)鍵字extends
或者super
在一起其實(shí)就是泛型的高級(jí)應(yīng)用:通配符。
我們來(lái)自定義一個(gè)泛型類(lèi)——PetHouse(寵物小屋),它有一些基本的動(dòng)作(可以住進(jìn)來(lái)一只寵物,也可以放出去):
public class PetHouse<T> {
private List<T> list;
public PetHouse() {
}
public void add(T item) {
list.add(item);
}
public T get() {
return list.get(0);
}
}
如果我們想要住進(jìn)去一只寵物,可以這樣定義小屋(其泛型為Pet):
PetHouse<Pet> petHouse = new PetHouse<>();
然后,我們讓小貓和小狗住進(jìn)去:
petHouse.add(new Cat());
petHouse.add(new Dog());
如果我們只想要住進(jìn)去一只小貓,打算這樣定義小屋:
PetHouse<Pet> petHouse = new PetHouse<Cat>();
但事實(shí)上,編譯器不允許我們這樣定義:因?yàn)榉盒筒恢苯又С窒蛏限D(zhuǎn)型。該怎么辦呢?
可以這樣定義小屋:
PetHouse<? extends Pet> petHouse = new PetHouse<Cat>();
也就是說(shuō),寵物小屋可以住進(jìn)去小貓,但它必須是寵物(Pet或者Pet的子類(lèi))而不是一只野貓。
但很遺憾,這個(gè)寵物小屋實(shí)際上住不了小貓,看下圖。
這是因?yàn)镴ava雖然支持泛型的向上轉(zhuǎn)型(使用 extends
通配符),但我們卻無(wú)法向其中添加任何東西——編譯器并不知道寵物小屋里要住的是小貓還是小狗,或者其他寵物,因此干脆什么都不讓住。
看到這,你一定非常疑惑,既然PetHouse<? extends Pet>
定義的寵物小屋什么也不讓住,那為什么還要這樣定義呢?思考一下。
泛型限定符有一描述:上界不存下界不取。
上界不存的原因:例如 List,編譯器只知道容器內(nèi)是 Father 及其子類(lèi),具體是什么類(lèi)型并不知道,編譯器在看到 extends 后面的 Father 類(lèi),只是標(biāo)上一個(gè) CAP#1
作為占位符,無(wú)論往里面插什么,編譯器都不知道能不能和 CAP#1
匹配,所以就不允許插入。
extends的作用:可以在初始化的時(shí)候存入一個(gè)值,并且能保證數(shù)據(jù)的穩(wěn)定性,只能取不能存。讀取出來(lái)的數(shù)據(jù)可以存在父類(lèi)或者基類(lèi)里。
下界不取的原因:下界限定了元素的最小粒度,實(shí)際上是放松了容器元素的類(lèi)型控制。例如 List, 元素是 Father 的基類(lèi),可以存入 Father 及其子類(lèi)。但編譯器并不知道哪個(gè)是 Father 的超類(lèi),如 Human。讀取的時(shí)候,自然不知道是什么類(lèi)型,只能返回 Object,這樣元素信息就全部丟失了。
super的作用:用于參數(shù)類(lèi)型限定。
PECS 原則:
1.頻繁往外讀取內(nèi)容的,適合用extends
2.經(jīng)常往里插入的,適合用super
上一篇:Java 數(shù)組,看這篇就夠了
下一篇:HashMap,難的不在Map,而在Hash
微信搜索「沉默王二」公眾號(hào),關(guān)注后回復(fù)「免費(fèi)視頻」獲取 500G 高質(zhì)量教學(xué)視頻(已分門(mén)別類(lèi))。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。