Java泛型食用筆記(一) -- 基本介紹
JDK5 將泛型引入,這是 Java 走向類型安全的一大步,然而黎炉,在學(xué)習(xí)和使用泛型的過(guò)程中,幾乎都會(huì)遇到令人沮喪的問(wèn)題嗽交。本系列試圖將 Java 的泛型解釋清楚卿嘲,幫助開(kāi)發(fā)者在開(kāi)發(fā)中正確的使用泛型。
1. 沒(méi)有泛型的糟糕世界
引入泛型的原因不少轮纫,其中一個(gè)重要原因就是為了容器類的優(yōu)雅實(shí)現(xiàn)腔寡。
在沒(méi)有泛型前,當(dāng)需要實(shí)現(xiàn)一個(gè)可以存儲(chǔ)任何類型對(duì)象的 Hashtable 時(shí)掌唾,會(huì)定義如下接口:
public class Hashtable {
...
public Object put(Object key, Object value) {...}
public Object get(Object key) {...}
...
}
當(dāng)你向 Hashtable 中插入對(duì)象時(shí)放前,比如一個(gè) String 對(duì)象,Hashtable 會(huì)把類型信息抹去糯彬,退化成 Object 對(duì)象進(jìn)行存儲(chǔ)凭语。此時(shí)只有你的腦袋中存有類型信息,當(dāng)你需要從 Hashtable 獲取元素時(shí)撩扒,必須對(duì)其進(jìn)行強(qiáng)制類型轉(zhuǎn)換才能獲得你所需的 String 對(duì)象似扔。
...
Hashtable h = new Hashtable();
h.put("string", "value");
String s = (String)h.get("string");
...
這樣的代碼直覺(jué)上就不太讓人放心。每次強(qiáng)制類型轉(zhuǎn)換搓谆,不僅增加冗余的代碼炒辉,同時(shí)也是一次忽略編譯器進(jìn)行靜態(tài)類型檢查的過(guò)程,如果轉(zhuǎn)換過(guò)程中出現(xiàn)錯(cuò)誤泉手,就會(huì)拋出 ClassCastException 異常黔寇,除非你能確保轉(zhuǎn)換無(wú)誤,否則你需要更過(guò)的代碼來(lái)進(jìn)行錯(cuò)誤處理斩萌。在泛型出來(lái)之前缝裤,這幾乎是無(wú)解題。
2. 泛型的引入
Java 泛型的核心就是告訴編譯器想使用什么類型颊郎,然后編譯器幫你處理一切細(xì)節(jié)憋飞。泛型也是一種參數(shù),只不過(guò)參數(shù)傳入的是類型姆吭。引入泛型可以讓一些實(shí)現(xiàn)更優(yōu)雅榛做。
使用泛型重新定義 Hashtable 及其接口:
public class Hashtable<K, V> {
...
public V put(K key, V value) {...}
public V get(K key) {...}
...
}
其中 <K, V>
即為類型參數(shù),其作用域?yàn)轭惗x的主體部分(除靜態(tài)成員)猾编。當(dāng)使用泛型類時(shí)瘤睹,你要將你所需使用的類型傳入。
...
Hashtable<String, String> h = new Hashtable<>();
h.put("string", "value");
String s = h.get("string");
...
使用泛型后的 Hashtable 類更加簡(jiǎn)潔答倡。我們來(lái)看下泛型帶來(lái)了什么:
- 將類型信息告訴編譯器
- 放入元素時(shí)編譯器進(jìn)行類型檢查
- 取出元素自動(dòng)轉(zhuǎn)換類型
編譯器在編譯期間就會(huì)進(jìn)行類型檢查轰传,防止在運(yùn)行期間出現(xiàn)類型錯(cuò)誤。
3. 泛型接口
泛型也可用于接口瘪撇,比如需要寫(xiě)一個(gè)生成器:
interface Gernerator<T> {
T next();
}
class DrinkGernerator implements Gernerator<Drink> {
private Drink[] drinks = {new Water(){}, new Coke(){}, new Coffee(){}};
private Random seed = new Random();
@Override
public Drink next() {
return drinks[seed.nextInt(3)];
}
}
abstract class Drink {
public abstract String name();
}
class Water extends Drink {
@Override
public String name() {
return "Water";
}
}
class Coke extends Drink {
@Override
public String name() {
return "Coke";
}
}
class Coffee extends Drink {
@Override
public String name() {
return "Coffee";
}
}
看上去是不是特別眼熟获茬,容器的迭代器 Iterator<E>
就是一個(gè)典型的生成器接口港庄。
4. 泛型方法
之前據(jù)的所有例子都是作用與整個(gè)類的,泛型也可以僅僅應(yīng)用在方法上恕曲,也就是接下來(lái)要介紹的泛型方法鹏氧。原則上,能夠使用泛型方法的時(shí)候就盡量避免使用泛型類佩谣,這會(huì)使你的代碼看上去更加清楚把还。另外,如果 static 方法需要使用泛型茸俭,只能使用泛型方法吊履。
泛型方法的使用方法就是將泛型參數(shù)置于返回值之前:
public class GernericMethod {
public static <T> void printClassName(T t) {
System.out.println(t.getClass().getName());
}
public static void main(String[] args) {
printClassName("string");
printClassName(1);
printClassName(2.1);
}
}
output:
java.lang.String
java.lang.Integer
java.lang.Double
看上去 printClassName
方法就像無(wú)限重載過(guò),無(wú)論傳入什么類型的參數(shù)调鬓,都可以順利執(zhí)行艇炎。這是因?yàn)榉盒头椒ㄔ谑褂脮r(shí),編譯器會(huì)進(jìn)行參數(shù)推斷腾窝,幫助我們找到具體的類型缀踪。
小結(jié)
泛型可以用于泛型類,泛型接口虹脯,泛型方法驴娃,并都有各自的使用場(chǎng)景。引入泛型后循集,可以避免很多蹩腳的類型轉(zhuǎn)換等操作托慨,讓代碼實(shí)現(xiàn)更為優(yōu)雅,看上去一切都很美好暇榴。接下來(lái)我們將探討 Java 泛型的實(shí)現(xiàn)原理及帶來(lái)的問(wèn)題,以幫助我們更好的理解和使用 Java 泛型