Java泛型食用筆記(一) -- 基本介紹
JDK5 將泛型引入,這是 Java 走向類型安全的一大步,然而,在學(xué)習(xí)和使用泛型的過程中,幾乎都會(huì)遇到令人沮喪的問題。本系列試圖將 Java 的泛型解釋清楚,幫助開發(fā)者在開發(fā)中正確的使用泛型。
1. 沒有泛型的糟糕世界
引入泛型的原因不少,其中一個(gè)重要原因就是為了容器類的優(yōu)雅實(shí)現(xiàn)。
在沒有泛型前,當(dāng)需要實(shí)現(xiàn)一個(gè)可以存儲(chǔ)任何類型對象的 Hashtable 時(shí),會(huì)定義如下接口:
public class Hashtable {
...
public Object put(Object key, Object value) {...}
public Object get(Object key) {...}
...
}
當(dāng)你向 Hashtable 中插入對象時(shí),比如一個(gè) String 對象,Hashtable 會(huì)把類型信息抹去,退化成 Object 對象進(jìn)行存儲(chǔ)。此時(shí)只有你的腦袋中存有類型信息,當(dāng)你需要從 Hashtable 獲取元素時(shí),必須對其進(jìn)行強(qiáng)制類型轉(zhuǎn)換才能獲得你所需的 String 對象。
...
Hashtable h = new Hashtable();
h.put("string", "value");
String s = (String)h.get("string");
...
這樣的代碼直覺上就不太讓人放心。每次強(qiáng)制類型轉(zhuǎn)換,不僅增加冗余的代碼,同時(shí)也是一次忽略編譯器進(jìn)行靜態(tài)類型檢查的過程,如果轉(zhuǎn)換過程中出現(xiàn)錯(cuò)誤,就會(huì)拋出 ClassCastException 異常,除非你能確保轉(zhuǎn)換無誤,否則你需要更過的代碼來進(jìn)行錯(cuò)誤處理。在泛型出來之前,這幾乎是無解題。
2. 泛型的引入
Java 泛型的核心就是告訴編譯器想使用什么類型,然后編譯器幫你處理一切細(xì)節(jié)。泛型也是一種參數(shù),只不過參數(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 類更加簡潔。我們來看下泛型帶來了什么:
- 將類型信息告訴編譯器
- 放入元素時(shí)編譯器進(jìn)行類型檢查
- 取出元素自動(dòng)轉(zhuǎn)換類型
編譯器在編譯期間就會(huì)進(jìn)行類型檢查,防止在運(yùn)行期間出現(xiàn)類型錯(cuò)誤。
3. 泛型接口
泛型也可用于接口,比如需要寫一個(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)用在方法上,也就是接下來要介紹的泛型方法。原則上,能夠使用泛型方法的時(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 方法就像無限重載過,無論傳入什么類型的參數(shù),都可以順利執(zhí)行。這是因?yàn)榉盒头椒ㄔ谑褂脮r(shí),編譯器會(huì)進(jìn)行參數(shù)推斷,幫助我們找到具體的類型。
小結(jié)
泛型可以用于泛型類,泛型接口,泛型方法,并都有各自的使用場景。引入泛型后,可以避免很多蹩腳的類型轉(zhuǎn)換等操作,讓代碼實(shí)現(xiàn)更為優(yōu)雅,看上去一切都很美好。接下來我們將探討 Java 泛型的實(shí)現(xiàn)原理及帶來的問題,以幫助我們更好的理解和使用 Java 泛型