1、為什么引入泛型
bug是編程的一部分,我們只能盡自己最大的能力減少出現(xiàn)bug的幾率,但是誰(shuí)也不能保證自己寫(xiě)出的程序不出現(xiàn)任何問(wèn)題开瞭。
錯(cuò)誤可分為兩種:編譯時(shí)錯(cuò)誤與運(yùn)行時(shí)錯(cuò)誤懒震。編譯時(shí)錯(cuò)誤在編譯時(shí)可以發(fā)現(xiàn)并排除,而運(yùn)行時(shí)錯(cuò)誤具有很大的不確定性惩阶,在程序運(yùn)行時(shí)才能發(fā)現(xiàn)挎狸,造成的后果可能是災(zāi)難性的。
使用泛型可以使錯(cuò)誤在編譯時(shí)被探測(cè)到断楷,從而增加程序的健壯性锨匆。
來(lái)看一個(gè)例子:
public class Box {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
按照聲明,其中的set()方法可以接受任何java對(duì)象作為參數(shù)(任何對(duì)象都是Object的子類(lèi))冬筒,假如在某個(gè)地方使用該類(lèi)恐锣,set()方法預(yù)期的輸入對(duì)象為Integer類(lèi)型,但是實(shí)際輸入的卻是String類(lèi)型舞痰,就會(huì)拋出一個(gè)運(yùn)行時(shí)錯(cuò)誤土榴,這個(gè)錯(cuò)誤在編譯階段是無(wú)法檢測(cè)的。例如:
Box box = new Box;
box.set("abc");
Integer a = (Integer)box.get(); //編譯時(shí)不會(huì)報(bào)錯(cuò)响牛,但是運(yùn)行時(shí)會(huì)報(bào)ClassCastException
運(yùn)用泛型改造上面的代碼:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
當(dāng)我們使用該類(lèi)時(shí)會(huì)指定T的具體類(lèi)型玷禽,該類(lèi)型參數(shù)可以是類(lèi)、接口呀打、數(shù)組等矢赁,但是不能是基本類(lèi)型。
比如:
Box<Integer> box = new Box<Integer>; //指定了類(lèi)型類(lèi)型為Integer
//box.set(“abc”); 該句在編譯時(shí)就會(huì)報(bào)錯(cuò)
box.set(new Integer(2));
Integer a = box.get(); //不用轉(zhuǎn)換類(lèi)型
可以看到贬丛,使用泛型還免除轉(zhuǎn)換操作撩银。
在引入泛型機(jī)制之前,要在方法中支持多個(gè)數(shù)據(jù)類(lèi)型豺憔,需要對(duì)方法進(jìn)行重載额获,在引入范型后,可以更簡(jiǎn)潔地解決此問(wèn)題恭应,更進(jìn)一步可以定義多個(gè)參數(shù)以及返回值之間的關(guān)系抄邀。
例如
public void write(Integer i, Integer[] ia);
public void write(Double d, Double[] da);
public void write(Long l, Long[] la);
范型版本為:
public <T> void write(T t, T[] ta);
總體來(lái)說(shuō),泛型機(jī)制能夠在定義類(lèi)昼榛、接口撤摸、方法時(shí)把“類(lèi)型”當(dāng)做參數(shù)使用,有點(diǎn)類(lèi)似于方法聲明中的形式參數(shù)褒纲,如此我們就能通過(guò)不同的輸入?yún)?shù)來(lái)實(shí)現(xiàn)程序的重用。不同的是钥飞,形式參數(shù)的輸入是值莺掠,而泛型參數(shù)的輸入是類(lèi)型。
2读宙、命名規(guī)則
類(lèi)型參數(shù)的命名有一套默認(rèn)規(guī)則彻秆,為了提高代碼的維護(hù)性和可讀性,強(qiáng)烈建議遵循這些規(guī)則。JDK中唇兑,隨處可見(jiàn)這些命名規(guī)則的應(yīng)用酒朵。
- E - Element (通常代表集合類(lèi)中的元素)
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. – 第二個(gè),第三個(gè)扎附,第四個(gè)類(lèi)型參數(shù)……
注意蔫耽,父類(lèi)定義的類(lèi)型參數(shù)不能被子類(lèi)繼承。
也可以同時(shí)聲明多個(gè)類(lèi)型變量留夜,用逗號(hào)分割匙铡,例如:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
下面的兩行代碼創(chuàng)建了OrderedPair
對(duì)象的兩個(gè)實(shí)例。
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
//也可以將new后面的類(lèi)型參數(shù)省略碍粥,簡(jiǎn)寫(xiě)為:
//Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
//也可以在尖括號(hào)內(nèi)使用帶有類(lèi)型變量的類(lèi)型變量鳖眼,例如:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
泛型是JDK 5.0之后才引入的,為了兼容性嚼摩,允許不指定泛型參數(shù)钦讳,但是如此一來(lái),編譯器就無(wú)法進(jìn)行類(lèi)型檢查枕面,在編程時(shí)愿卒,最好明確指定泛型參數(shù)。
同樣膊畴,在方法中也可是使用泛型參數(shù)掘猿,并且該參數(shù)的使用范圍僅限于方法體內(nèi)。例如:
public class Util {
//該方法用于比較兩個(gè)Pair對(duì)象是否相等唇跨。
//泛型參數(shù)必須寫(xiě)在方法返回類(lèi)型boolean之前
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
//實(shí)際上稠通,編譯器可以通過(guò)Pair當(dāng)中的類(lèi)型來(lái)推斷compare需要使用的類(lèi)型,所以可以簡(jiǎn)寫(xiě)為:
// boolean same = Util. compare(p1, p2);
有時(shí)候我們想讓類(lèi)型參數(shù)限定在某個(gè)范圍之內(nèi)买猖,就需要用到extends
關(guān)鍵字(extends
后面可以跟一個(gè)接口改橘,這里的extends
既可以表示繼承了某個(gè)類(lèi),也可以表示實(shí)現(xiàn)了某個(gè)接口)玉控,例如飞主,我們想讓參數(shù)是數(shù)字類(lèi)型:
class Box<T extends Number> { //類(lèi)型參數(shù)限定為Number的子類(lèi)
private T t;
public Box(T t) {
this.t = t;
}
public void print() {
System.out.println(t.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> box1 = new Box<Integer>(new Integer(2));
box1.print(); //打印結(jié)果:java.lang.Integer
Box<Double> box2 = new Box<Double>(new Double(1.2));
box2.print(); //打印結(jié)果:java.lang.Double
Box<String> box2 = new Box<String>(new String("abc")); //報(bào)錯(cuò),因?yàn)镾tring類(lèi)型不是Number的子類(lèi)
box2.print();
}
}
如果加入多個(gè)限定高诺,可以用“&”連接起來(lái)碌识,但是由于java是單繼承,多個(gè)限定中最多只能有一個(gè)類(lèi)虱而,而且必須放在第一個(gè)位置筏餐。例如:
class Box<T extends Number & Cloneable & Comparable> {
//該類(lèi)型必須為Number的子類(lèi)并且實(shí)現(xiàn)了Cloneable接口和Comparable接口。
//……
}
3牡拇、泛型類(lèi)的繼承
java是面向?qū)ο蟮母呒?jí)語(yǔ)言魁瞪,在一個(gè)接受A類(lèi)參數(shù)的地方傳入一個(gè)A的子類(lèi)是允許的穆律,例如:
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // 因?yàn)镮nteger是Object的子類(lèi)
這種特性同樣適用于類(lèi)型參數(shù),例如:
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // Integer是Number的子類(lèi)
box.add(new Double(10.1)); // Double同樣是Number的子類(lèi)
但是导俘,有一種情況很容易引起混淆峦耘,例如:
//該方法接受的參數(shù)類(lèi)型為Box<Number>
public void boxTest(Box<Number> n) {
//……
}
//下面兩種調(diào)用都會(huì)報(bào)錯(cuò)
boxTest(Box<Integer>);
boxTest(Box<Double>);
雖然Integer和Double都是Number的子類(lèi),但是Box<Integer>與Box<Double>并不是Box<Number>的子類(lèi)旅薄,不存在繼承關(guān)系辅髓。Box<Integer>與Box<Double>的共同父類(lèi)是Object。
以JDK中的集合類(lèi)為例赋秀,ArrayList<E> 實(shí)現(xiàn)了 List<E>接口利朵,List<E>接口繼承了 Collection<E>接口,所以猎莲,ArrayList<String>是List<String>的子類(lèi)绍弟,而非List<Integer>的子類(lèi)。三者的繼承關(guān)系如下:
4著洼、類(lèi)型推斷
先來(lái)看一個(gè)例子:
public class Demo {
static <T> T pick(T a1, T a2) {
return a2;
}
}
靜態(tài)方法pick()在三個(gè)地方使用了泛型樟遣,分別限定了兩個(gè)輸入?yún)?shù)的類(lèi)型與返回類(lèi)型。調(diào)用該方法的代碼如下:
Integer ret = Demo.<Integer> pick(new Integer(1), new Integer(2));
前文已經(jīng)提到身笤,上面的代碼可以簡(jiǎn)寫(xiě)為:
Integer ret = Demo.pick(new Integer(1), new Integer(2));
因?yàn)閖ava編譯器會(huì)根據(jù)方法內(nèi)的參數(shù)類(lèi)型推斷出該方法返回的類(lèi)型應(yīng)該為Integer豹悬,這種機(jī)制稱(chēng)為類(lèi)型推斷(Type Inference)。
那么問(wèn)題來(lái)了液荸,加入兩個(gè)輸入?yún)?shù)為不同的類(lèi)型瞻佛,應(yīng)該返回什么類(lèi)型呢?
例如:
pick("d", new ArrayList<String>());
第一個(gè)參數(shù)為String類(lèi)型娇钱,第二個(gè)參數(shù)為ArrayList類(lèi)型伤柄,java編譯器就會(huì)根據(jù)這兩個(gè)參數(shù)類(lèi)型來(lái)推斷,盡量使返回類(lèi)型為最明確的一種文搂。本例中适刀,String與ArrayList都實(shí)現(xiàn)了同樣的接口——Serializable,當(dāng)然煤蹭,他們也是Object的子類(lèi)笔喉,Serializable類(lèi)型顯然比Object類(lèi)型更加明確,因?yàn)樗姆秶「?xì)分硝皂,所以最終的返回類(lèi)型應(yīng)該為Serializable:
Serializable s = pick("d", new ArrayList<String>());
在泛型類(lèi)實(shí)例化的時(shí)候同樣可以利用這種機(jī)制簡(jiǎn)化代碼常挚,需要注意的是,尖括號(hào)“<>”在此時(shí)是不能省略的稽物。例如:
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
//編譯器能推斷出后面的類(lèi)型待侵,所以可以簡(jiǎn)化為:
Map<String, List<String>> myMap = new HashMap<>();
//但是,不能簡(jiǎn)化為:
Map<String, List<String>> myMap = new HashMap();
//因?yàn)镠ashMap()是HashMap原始類(lèi)型(Raw Type)的構(gòu)造函數(shù)姨裸,而非HashMap<String, List<String>>的構(gòu)造函數(shù)秧倾,如果不加“<>”編譯器不會(huì)進(jìn)行類(lèi)型檢查
5、通配符
上文中我們提到過(guò)一個(gè)例子:
public void boxTest(Box<Number> n){
//……
}
該方法只能接受Box<Number>這一種類(lèi)型的參數(shù)傀缩,當(dāng)我們輸入一個(gè)Box<Double>或者Box<Integer>時(shí)會(huì)報(bào)錯(cuò)那先,盡管Integer與Double是Number的子類(lèi)∩募瑁可是如果我們希望該方法可以接受Number以及它的任何子類(lèi)售淡,該怎么辦呢?
這時(shí)候就要用到通配符了慷垮,改寫(xiě)如下:
public void boxTest(Box<? extends Number> n){
//……
}
? extends Number
就代表可以接受Number以及它的子類(lèi)作為參數(shù)揖闸。這種聲明方式被稱(chēng)為上限通配符(upper bounded wildcard)。
相反地料身,如果我們希望該方法可以接受Integer汤纸,Number以及Object類(lèi)型的參數(shù)怎么辦呢?應(yīng)該使用下限通配符(lower bounded wildcard):
public void boxTest(Box<? super Integer> n){
//……
}
? super Integer
代表可以接受Integer以及它的父類(lèi)作為參數(shù)芹血。
如果類(lèi)型參數(shù)中既沒(méi)有extends 關(guān)鍵字贮泞,也沒(méi)有super關(guān)鍵字,只有一個(gè)?幔烛,代表無(wú)限定通配符(Unbounded Wildcards)啃擦。
通常在兩種情況下會(huì)使用無(wú)限定通配符:
- 如果正在編寫(xiě)一個(gè)方法,可以使用Object類(lèi)中提供的功能來(lái)實(shí)現(xiàn)
- 代碼實(shí)現(xiàn)的功能與類(lèi)型參數(shù)無(wú)關(guān)饿悬,比如
List.clear()
與List.size()
方法令蛉,還有經(jīng)常使用的Class<?>方法,其實(shí)現(xiàn)的功能都與類(lèi)型參數(shù)無(wú)關(guān)狡恬。
來(lái)看一個(gè)例子:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
該方法只能接受List<Object>型的參數(shù)珠叔,不接受其他任何類(lèi)型的參數(shù)。但是傲宜,該方法實(shí)現(xiàn)的功能與List之中參數(shù)類(lèi)型沒(méi)有關(guān)系运杭,所以我們希望它可以接受包含任何類(lèi)型的List參數(shù)。代碼改動(dòng)如下:
public static void printList(List<?> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
需要特別注意的是函卒,List<?>與List<Object>并不相同辆憔,無(wú)論A是什么類(lèi)型,List<A>是List<?>的子類(lèi)报嵌,但是虱咧,List<A>不是List<Object>的子類(lèi)。
例如:
List<Number> lb = new ArrayList<>();
List<Integer> la = lb; // 會(huì)報(bào)編譯錯(cuò)誤锚国,盡管Integer是Number的子類(lèi)腕巡,但是List<Integer>不是List<Number>的子類(lèi)
List<Integer>與List<Number>的關(guān)系如下:
所以,下面的代碼是正確的:
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // 不會(huì)報(bào)錯(cuò)血筑, List<? extends Integer> 是 List<? extends Number>的子類(lèi)
下面這張圖介紹了上限通配符绘沉、下限通配符煎楣、無(wú)限定通配符之間的關(guān)系:
編譯器可以通過(guò)類(lèi)型推斷機(jī)制來(lái)決定通配符的類(lèi)型,這種情況被稱(chēng)為通配符捕獲车伞。大多時(shí)候我們不必?fù)?dān)心通配符捕獲择懂,除非編譯器報(bào)出了包含“capture of”的錯(cuò)誤。例如:
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0)); //會(huì)報(bào)編譯錯(cuò)誤
}
}
上例中另玖,調(diào)用List.set(int,E)
方法的時(shí)候困曙,編譯器無(wú)法推斷i.get(0)
是什么類(lèi)型,就會(huì)報(bào)錯(cuò)谦去。
我們可以借助一個(gè)私有的可以捕獲通配符的helper方法來(lái)解決這種錯(cuò)誤:
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// 該方法可以確保編譯器通過(guò)通配符捕獲來(lái)推斷出參數(shù)類(lèi)型
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}
按照約定俗成的習(xí)慣慷丽,helper方法的命名方法為“原始方法”+“helper”,上例中鳄哭,原始方法為“foo”要糊,所以命名為“fooHelper”。
關(guān)于什么時(shí)候該使用上限通配符窃诉,什么時(shí)候該使用下限通配符杨耙,應(yīng)該遵循一下幾項(xiàng)指導(dǎo)規(guī)則。
首先將變量分為in-變量
與out-變量
:in-變量
持有為當(dāng)前代碼服務(wù)的數(shù)據(jù)飘痛,out-變量
持有其他地方需要使用的數(shù)據(jù)珊膜。
例如copy(src, dest)
方法實(shí)現(xiàn)了從src源頭將數(shù)據(jù)復(fù)制到dest目的地的功能,那么src就是in-變量
宣脉,而dest就是out-變量
车柠。當(dāng)然,在一些情況下塑猖,一個(gè)變量可能既是in-變量
也是out-變量
竹祷。
- in-變量使用上限通配符;
- out-變量使用下限通配符羊苟;
- 當(dāng)in-變量可以被Object類(lèi)中的方法訪(fǎng)問(wèn)時(shí)塑陵,使用無(wú)限定通配符;
- 一個(gè)變量既是in-變量也是out-變量時(shí)蜡励,不使用通配符
注意令花,上面的規(guī)則不適用于方法的返回類(lèi)型。
6凉倚、類(lèi)型擦除
java編譯器在處理泛型的時(shí)候兼都,會(huì)做下面幾件事:
- 將沒(méi)有限定的類(lèi)型參數(shù)用Object替換,保證class文件中只含有正常的類(lèi)稽寒、接口與方法扮碧;
- 在必要的時(shí)候進(jìn)行類(lèi)型轉(zhuǎn)換,保證類(lèi)型安全;
- 在泛型的繼承上使用橋接方法(bridge methods)保持多態(tài)性慎王。
這類(lèi)操作被稱(chēng)為類(lèi)型擦除(Type Erasure)蚓土。
例如:
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() {
return data;
}
// ...
}
該類(lèi)中的T沒(méi)有被extends或者super限定,會(huì)被編譯器替換成Object:
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
// ...
}
如果T加了限定柬祠,編譯器會(huì)將它替換成合適的類(lèi)型:
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
改造成:
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() {
return data;
}
// ...
}
方法中的類(lèi)型擦除與之類(lèi)似北戏。
有時(shí)候類(lèi)型擦除會(huì)產(chǎn)生一些我們預(yù)想不到的情況,下面通過(guò)一個(gè)例子來(lái)分析它是如何產(chǎn)生的漫蛔。
public class Node<T> {
public T data;
public Node(T data) {
this.data = data;
}
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) {
super(data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
上面的代碼定義了兩個(gè)類(lèi),MyNode類(lèi)繼承了Node類(lèi)旧蛾,然后運(yùn)行下面的代碼:
MyNode mn = new MyNode(5);
Node n = mn;
n.setData("Hello");
Integer x = mn.data; // 拋出ClassCastException異常
上面的代碼在類(lèi)型擦除之后會(huì)轉(zhuǎn)換成下面的形式:
MyNode mn = new MyNode(5);
Node n = (MyNode)mn;
n.setData("Hello");
Integer x = (String)mn.data; // 拋出ClassCastException異常
我們來(lái)看看代碼是怎么執(zhí)行的:
- (1)
n.setData("Hello")
調(diào)用的其實(shí)是MyNode類(lèi)的setData(Object)方法(從Node類(lèi)繼承的)莽龟; - (2)n引用的對(duì)象中的data字段被賦值一個(gè)String變量;
- (3)mn引用的相同對(duì)象中的data預(yù)期為Integer類(lèi)型(mn為Node<Integer>類(lèi)型)锨天;
- (4)第四行代碼試圖將一個(gè)String賦值給Integer類(lèi)型的變量毯盈,所以引發(fā)了ClassCastException異常。
當(dāng)編譯一個(gè)繼承了帶有參數(shù)化泛型的類(lèi)或借口時(shí)病袄,編譯器會(huì)根據(jù)需要?jiǎng)?chuàng)建被稱(chēng)為bridge method
的橋接方法搂赋,這是類(lèi)型擦除中的一部分。
上例中MyNode繼承了Node<Integer>類(lèi)益缠,類(lèi)型擦除之后脑奠,代碼變?yōu)椋?/p>
class MyNode extends Node {
//編譯器添加的橋接方法
public void setData(Object data) {
setData((Integer) data);
}
// MyNode的該方法并沒(méi)有覆寫(xiě)父類(lèi)的setData(Object data)方法,因?yàn)閰?shù)類(lèi)型不一樣
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
// ...
}
7幅慌、注意事項(xiàng)
為了高效地使用泛型宋欺,應(yīng)該注意下面幾個(gè)方面:
(1)不能用基本類(lèi)型實(shí)例化類(lèi)型參數(shù)
例如
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// ...
}
當(dāng)創(chuàng)建一個(gè)Pair類(lèi)時(shí),不能用基本類(lèi)型來(lái)替代K胰伍,V兩個(gè)類(lèi)型參數(shù)齿诞。
Pair<int, char> p = new Pair<>(8, 'a'); // 編譯錯(cuò)誤
Pair<Integer, Character> p = new Pair<>(8, 'a'); //正確寫(xiě)法
(2)不可實(shí)例化類(lèi)型參數(shù)
例如:
public static <E> void append(List<E> list) {
E elem = new E(); // 編譯錯(cuò)誤
list.add(elem);
}
但是,我們可以通過(guò)反射實(shí)例化帶有類(lèi)型參數(shù)的對(duì)象:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // 正確
list.add(elem);
}
List<String> ls = new ArrayList<>();
append(ls, String.class); //傳入類(lèi)型參數(shù)的Class對(duì)象
(3)不能在靜態(tài)字段上使用泛型
通過(guò)一個(gè)反例來(lái)說(shuō)明:
public class MobileDevice<T> {
private static T os; //假如我們定義了一個(gè)帶泛型的靜態(tài)字段
// ...
}
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();
因?yàn)殪o態(tài)變量是類(lèi)變量骂租,被所有實(shí)例共享祷杈,此時(shí),靜態(tài)變量os的真實(shí)類(lèi)型是什么呢渗饮?顯然不能同時(shí)是Smartphone但汞、Pager、TabletPC抽米。
這就是為什么不能在靜態(tài)字段上使用泛型的原因特占。
(4)不能對(duì)帶有參數(shù)化類(lèi)型的類(lèi)使用cast或instanceof方法
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // 編譯錯(cuò)誤
// ...
}
}
傳給蓋該方法的參數(shù)化類(lèi)型集合為:
S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }
運(yùn)行環(huán)境并不會(huì)跟蹤類(lèi)型參數(shù),所以分辨不出ArrayList<Integer>與ArrayList<String>云茸,我們能做的至多是使用無(wú)限定通配符來(lái)驗(yàn)證list是否為ArrayList:
public static void rtti(List<?> list) {
if (list instanceof ArrayList<?>) { // 正確
// ...
}
}
同樣是目,不能將參數(shù)轉(zhuǎn)換成一個(gè)帶參數(shù)化類(lèi)型的對(duì)象,除非它的參數(shù)化類(lèi)型為無(wú)限定通配符(<?>):
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li; // 編譯錯(cuò)誤
當(dāng)然标捺,如果編譯器知道參數(shù)化類(lèi)型肯定有效懊纳,是允許這種轉(zhuǎn)換的:
List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1; // 允許轉(zhuǎn)變揉抵,類(lèi)型參數(shù)沒(méi)變化
(5)不能創(chuàng)建帶有參數(shù)化類(lèi)型的數(shù)組
例如:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // 編譯錯(cuò)誤
下面通過(guò)兩段代碼來(lái)解釋為什么不行。先來(lái)看一個(gè)正常的操作:
Object[] strings = new String[2];
strings[0] = "hi"; // 插入正常
strings[1] = 100; //報(bào)錯(cuò)嗤疯,因?yàn)?00不是String類(lèi)型
同樣的操作冤今,如果使用的是泛型數(shù)組,就會(huì)出問(wèn)題:
Object[] stringLists = new List<String>[]; // 該句代碼實(shí)際上會(huì)報(bào)錯(cuò)茂缚,但是我們先假定它可以執(zhí)行
stringLists[0] = new ArrayList<String>(); // 插入正常
stringLists[1] = new ArrayList<Integer>(); // 該句代碼應(yīng)該報(bào)ArrayStoreException的異常戏罢,但是運(yùn)行環(huán)境探測(cè)不到
(6)不能創(chuàng)建、捕獲泛型異常
泛型類(lèi)不能直接或間接繼承Throwable類(lèi)
class MathException<T> extends Exception { /* ... */ } //編譯錯(cuò)誤
class QueueFullException<T> extends Throwable { /* ... */} // 編譯錯(cuò)誤
方法不能捕獲泛型異常:
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // 編譯錯(cuò)誤
// ...
}
}
但是脚囊,我們可以在throw子句中使用類(lèi)型參數(shù):
class Parser<T extends Exception> {
public void parse(File file) throws T { // 正確
// ...
}
}
(7)不能重載經(jīng)過(guò)類(lèi)型擦除后形參轉(zhuǎn)化為相同原始類(lèi)型的方法
先來(lái)看一段代碼:
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
打印結(jié)果可能與我們猜測(cè)的不一樣龟糕,打印出的是true,而非false悔耘,因?yàn)橐粋€(gè)泛型類(lèi)的所有實(shí)例在運(yùn)行時(shí)具有相同的運(yùn)行時(shí)類(lèi)(class)讲岁,而不管他們的實(shí)際類(lèi)型參數(shù)。
事實(shí)上衬以,泛型之所以叫泛型缓艳,就是因?yàn)樗鼘?duì)所有其可能的類(lèi)型參數(shù),有同樣的行為看峻;同樣的類(lèi)可以被當(dāng)作許多不同的類(lèi)型阶淘。
認(rèn)識(shí)到了這一點(diǎn),再來(lái)看下面的例子:
public class Example {
public void print(Set<String> strSet) { } //編譯錯(cuò)誤
public void print(Set<Integer> intSet) { } //編譯錯(cuò)誤
}
因?yàn)镾et<String>與Set<Integer>本質(zhì)上屬于同一個(gè)運(yùn)行時(shí)類(lèi)备籽,在經(jīng)過(guò)類(lèi)型擦出以后舶治,上面的兩個(gè)方法會(huì)共享一個(gè)方法簽名,相當(dāng)于一個(gè)方法车猬,所以重載出錯(cuò)霉猛。