Java十四篇:泛型
泛型是一種“代碼模板”,可以用一套代碼套用各種類型。

反方向的鐘

什么是泛型
在講解什么是泛型之前,我們先觀察Java標(biāo)準(zhǔn)庫(kù)提供的ArrayList
,它可以看作“可變長(zhǎng)度”的數(shù)組,因?yàn)橛闷饋?lái)比數(shù)組更方便。
實(shí)際上ArrayList
內(nèi)部就是一個(gè)Object[]
數(shù)組,配合存儲(chǔ)一個(gè)當(dāng)前分配的長(zhǎng)度,就可以充當(dāng)“可變數(shù)組”:
publicclass ArrayList {
? ?private Object[] array;
? ?privateint size;
? ?public void add(Object e) {...}
? ?public void remove(int index) {...}
? ?public Object get(int index) {...}
}
如果用上述ArrayList
存儲(chǔ)String
類型,會(huì)有這么幾個(gè)缺點(diǎn):
需要強(qiáng)制轉(zhuǎn)型;
不方便,易出錯(cuò)。
例如,代碼必須這么寫(xiě):
ArrayList list = new ArrayList();
list.add("Hello");
// 獲取到Object,必須強(qiáng)制轉(zhuǎn)型為String:
String first = (String) list.get(0);
很容易出現(xiàn)ClassCastException,因?yàn)槿菀住罢`轉(zhuǎn)型”:
list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);
要解決上述問(wèn)題,我們可以為String
單獨(dú)編寫(xiě)一種ArrayList
:
publicclass StringArrayList {
? ?private String[] array;
? ?privateint size;
? ?public void add(String e) {...}
? ?public void remove(int index) {...}
? ?public String get(int index) {...}
}
這樣一來(lái),存入的必須是String
,取出的也一定是String
,不需要強(qiáng)制轉(zhuǎn)型,因?yàn)榫幾g器會(huì)強(qiáng)制檢查放入的類型:
StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// 編譯錯(cuò)誤: 不允許放入非String類型:
list.add(new Integer(123));
問(wèn)題暫時(shí)解決。
然而,新的問(wèn)題是,如果要存儲(chǔ)Integer
,還需要為Integer
單獨(dú)編寫(xiě)一種ArrayList
:
publicclass IntegerArrayList {
? ?private Integer[] array;
? ?privateint size;
? ?public void add(Integer e) {...}
? ?public void remove(int index) {...}
? ?public Integer get(int index) {...}
}
實(shí)際上,還需要為其他所有class單獨(dú)編寫(xiě)一種ArrayList
:
LongArrayList
DoubleArrayList
PersonArrayList
...
這是不可能的,JDK的class就有上千個(gè),而且它還不知道其他人編寫(xiě)的class。
為了解決新的問(wèn)題,我們必須把ArrayList
變成一種模板:ArrayList<T>
,代碼如下:
publicclass ArrayList<T> {
? ?private T[] array;
? ?privateint size;
? ?public void add(T e) {...}
? ?public void remove(int index) {...}
? ?public T get(int index) {...}
}
T
可以是任何class。這樣一來(lái),我們就實(shí)現(xiàn)了:編寫(xiě)一次模版,可以創(chuàng)建任意類型的ArrayList
:
// 創(chuàng)建可以存儲(chǔ)String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 創(chuàng)建可以存儲(chǔ)Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 創(chuàng)建可以存儲(chǔ)Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
因此,泛型就是定義一種模板,例如ArrayList<T>
,然后在代碼中為用到的類創(chuàng)建對(duì)應(yīng)的ArrayList<類型>
:
ArrayList<String> strList = new ArrayList<String>();
由編譯器針對(duì)類型作檢查:
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!
這樣一來(lái),既實(shí)現(xiàn)了編寫(xiě)一次,萬(wàn)能匹配,又通過(guò)編譯器保證了類型安全:這就是泛型。
向上轉(zhuǎn)型
在Java標(biāo)準(zhǔn)庫(kù)中的ArrayList<T>
實(shí)現(xiàn)了List<T>
接口,它可以向上轉(zhuǎn)型為List<T>
:
publicclass ArrayList<T> implements List<T> {
? ?...
}
List<String> list = new ArrayList<String>();
即類型ArrayList<T>
可以向上轉(zhuǎn)型為List<T>
。
要特別注意:不能把ArrayList<Integer>
向上轉(zhuǎn)型為ArrayList<Number>
或List<Number>
。
這是為什么呢?假設(shè)ArrayList<Integer>
可以向上轉(zhuǎn)型為ArrayList<Number>
,觀察一下代碼:
// 創(chuàng)建ArrayList<Integer>類型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一個(gè)Integer:
integerList.add(new Integer(123));
// “向上轉(zhuǎn)型”為ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一個(gè)Float,因?yàn)镕loat也是Number:
numberList.add(new Float(12.34));
// 從ArrayList<Integer>獲取索引為1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!
我們把一個(gè)ArrayList<Integer>
轉(zhuǎn)型為ArrayList<Number>
類型后,這個(gè)ArrayList<Number>
就可以接受Float
類型,因?yàn)?code>Float是Number
的子類。但是,ArrayList<Number>
實(shí)際上和ArrayList<Integer>
是同一個(gè)對(duì)象,也就是ArrayList<Integer>
類型,它不可能接受Float
類型, 所以在獲取Integer
的時(shí)候?qū)a(chǎn)生ClassCastException
。
實(shí)際上,編譯器為了避免這種錯(cuò)誤,根本就不允許把ArrayList<Integer>
轉(zhuǎn)型為ArrayList<Number>
。
ArrayList和ArrayList兩者完全沒(méi)有繼承關(guān)系。
使用泛型
使用泛型時(shí),把泛型參數(shù)<T>
替換為需要的class類型,例如:ArrayList<String>
,ArrayList<Number>
等;
可以省略編譯器能自動(dòng)推斷出的類型,例如:List<String> list = new ArrayList<>();
;
不指定泛型參數(shù)類型時(shí),編譯器會(huì)給出警告,且只能將<T>
視為Object
類型;
可以在接口中定義泛型類型,實(shí)現(xiàn)此接口的類必須實(shí)現(xiàn)正確的泛型類型。
編寫(xiě)泛型
編寫(xiě)泛型類比普通類要復(fù)雜。通常來(lái)說(shuō),泛型類一般用在集合類中,例如ArrayList<T>
,我們很少需要編寫(xiě)泛型類。
如果我們確實(shí)需要編寫(xiě)一個(gè)泛型類,那么,應(yīng)該如何編寫(xiě)它?
可以按照以下步驟來(lái)編寫(xiě)一個(gè)泛型類。
首先,按照某種類型,例如:String
,來(lái)編寫(xiě)類:
publicclass Pair {
? ?private String first;
? ?private String last;
? ?public Pair(String first, String last) {
? ? ? ?this.first = first;
? ? ? ?this.last = last;
? ?}
? ?public String getFirst() {
? ? ? ?return first;
? ?}
? ?public String getLast() {
? ? ? ?return last;
? ?}
}
然后,標(biāo)記所有的特定類型,這里是String
:
publicclass Pair {
? ?private String first;
? ?private String last;
? ?public Pair(String first, String last) {
? ? ? ?this.first = first;
? ? ? ?this.last = last;
? ?}
? ?public String getFirst() {
? ? ? ?return first;
? ?}
? ?public String getLast() {
? ? ? ?return last;
? ?}
}
最后,把特定類型String
替換為T
,并申明<T>
:
publicclass Pair<T> {
? ?private T first;
? ?private T last;
? ?public Pair(T first, T last) {
? ? ? ?this.first = first;
? ? ? ?this.last = last;
? ?}
? ?public T getFirst() {
? ? ? ?return first;
? ?}
? ?public T getLast() {
? ? ? ?return last;
? ?}
}
熟練后即可直接從T
開(kāi)始編寫(xiě)。
靜態(tài)方法
編寫(xiě)泛型類時(shí),要特別注意,泛型類型<T>
不能用于靜態(tài)方法。
多個(gè)泛型類型
泛型可以同時(shí)定義多種類型,例如Map<K, V>
。
擦拭法
Java的泛型是采用擦拭法實(shí)現(xiàn)的;
擦拭法決定了泛型<T>
:
不能是基本類型,例如:
int
;不能獲取帶泛型類型的
Class
,例如:Pair<String>.class
;不能判斷帶泛型類型的類型,例如:
x instanceof Pair<String>
;不能實(shí)例化
T
類型,例如:new T()
。
泛型方法要防止重復(fù)定義方法,例如:public boolean equals(T obj)
;
子類可以獲取父類的泛型類型<T>
。
通配符
使用類似<? extends Number>
通配符作為方法參數(shù)時(shí)表示:
方法內(nèi)部可以調(diào)用獲取
Number
引用的方法,例如:Number n = obj.getFirst();
;方法內(nèi)部無(wú)法調(diào)用傳入
Number
引用的方法(null
除外),例如:obj.setFirst(Number n);
。
即一句話總結(jié):使用extends
通配符表示可以讀,不能寫(xiě)。
使用類似<T extends Number>
定義泛型類時(shí)表示:
泛型類型限定為
Number
以及Number
的子類。
super
使用類似<? super Integer>
通配符作為方法參數(shù)時(shí)表示:
方法內(nèi)部可以調(diào)用傳入
Integer
引用的方法,例如:obj.setFirst(Integer n);
;方法內(nèi)部無(wú)法調(diào)用獲取
Integer
引用的方法(Object
除外),例如:Integer n = obj.getFirst();
。
即使用super
通配符表示只能寫(xiě)不能讀。
使用extends
和super
通配符要遵循PECS原則。
無(wú)限定通配符<?>
很少使用,可以用<T>
替換,同時(shí)它是所有<T>
類型的超類。
泛型和反射
Java的部分反射API也是泛型、例如:Class<T>
就是泛型:Constructor<T>
;
可以聲明帶泛型的數(shù)組,但不能直接創(chuàng)建帶泛型的數(shù)組,必須強(qiáng)制轉(zhuǎn)型;
可以通過(guò)Array.newInstance(Class<T>, int)
創(chuàng)建T[]
數(shù)組,需要強(qiáng)制轉(zhuǎn)型;
同時(shí)使用泛型和可變參數(shù)時(shí)需要特別小心。
結(jié)尾
下一章我們來(lái)講講‘反射’,他在Java中的應(yīng)用是非常廣泛的,學(xué)會(huì)了你會(huì)又掌握了一項(xiàng)技巧。
總結(jié):
1.集合中也大量了應(yīng)用的范型,來(lái)簡(jiǎn)化代碼,讓代碼更加的健壯。
2.范型有的時(shí)候就是一個(gè)模板,你按照他的方式去實(shí)現(xiàn),就可以完成你的需求和功能
3.在反射中.class也是應(yīng)用到了反射,去獲取私有類中的對(duì)象和屬性
4.范型使得代碼更加的簡(jiǎn)潔和規(guī)范了
5.范型可以向上轉(zhuǎn)型,向他的父類直接定義
