泛型引入java語言已經(jīng)有很長一段時間了,在JDK5出來的時候也非常認真地學(xué)習(xí)過,不過學(xué)習(xí)的資料都是網(wǎng)上泛濫并且重復(fù)的教程。這幾天下了《The Java Programming Language》的第4版,準備把jdk5引入的新東西再重新系統(tǒng)地學(xué)習(xí)一次,同時再次回顧下java基礎(chǔ)。今天記錄下學(xué)習(xí)泛型那一章的注意點。
一、泛型類型的聲明
1.需要著重注意的一點,比如聲明類Cell<E>:
package net.rubyeye.javaprogramming.generic;
public class Cell<E> {
private Cell<E> next;
private E element;
public Cell(E element) {
this.element = element;
}
public Cell(E element, Cell<E> next) {
this.next = next;
this.element = element;
}
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
public Cell<E> getNext() {
return next;
}
public void setNext(Cell<E> next) {
this.next = next;
}
}
然后如此使用:
Cell<String> strCell = new Cell<String>("Hello");
Cell<Integer> intCell = new Cell<Integer>(25);
那么Cell<String>和Cell<Integer>是兩個類嗎?不,他們是同一個類,通過下面的實驗證明:
assertTrue(strCell.getClass() == intCell.getClass()));
java泛型的實現(xiàn)采用的“擦拭法”,Cell<E>仍然是一個類,無論E被任何具體的類型所替代。
2.泛型的類型參數(shù)不能用于static變量、static方法和static初始化,比如下面的使用方式都不能編譯通過:
public class Cell<E> {
private static Cell<E> next;
private static void test(E e){
}
同樣,靜態(tài)方法是與類相關(guān)聯(lián)的,調(diào)用也只能通過類,假設(shè)Cell有一個靜態(tài)方法test,怎么調(diào)用才是正確的呢?
Cell<E>.test(); //編譯錯誤
Cell<String>.test(); //同樣編譯錯誤
Cell.test(); //正確的方式
類似的,泛型的類型參數(shù)不能用于聲明數(shù)組類型,比如下面的代碼同樣無法編譯通過:
class SingleLinkQueue<E> {
// 
public E[] toArray() {
//
}
}
3.類型參數(shù)可以繼承其他的類和接口,如果有多個接口可以用&符號連接,通過extend參數(shù)限制了類型參數(shù)的范圍,比如:
interface SortedCharSeqCollection<E extends Comparable<E>
& CharSequence> {
//
sorted char sequence collection methods 
}
SortedCharSeqCollection的類型參數(shù)E強制繼承自Comparable和CharSequence接口,也就是替代的具體的類型參數(shù)必須實現(xiàn)這兩個接口,從而限制了類型參數(shù)(type parameter)。
4.比較有趣的內(nèi)部類的泛型,對于靜態(tài)內(nèi)部類的類型參數(shù)可以與外部類的類型參數(shù)名不一樣,靜態(tài)內(nèi)部類的類型參數(shù)與外部類的類型參數(shù)其實沒有一點關(guān)系,比如:
class SingleLinkQueue<E> {
static class Cell<E> {
private Cell<E> next;
private E element;
public Cell(E element) {
this.element = element;
}
public Cell(E element, Cell<E> next) {
this.element = element;
this.next = next;
}
public E getElement() {
return element;
}
/*
rest of Cell methods as before
*/
}
protected Cell<E> head;
protected Cell<E> tail;
/*
rest of SingleLinkQueue methods as before
*/
}
Cell<E>類的聲明和SingleLinkQueue<E> 兩個類中的E僅僅是名稱相同,他們之間的關(guān)聯(lián)是通過head和tail的聲明才關(guān)聯(lián)在一起,你可以將Cell<E>中的E改成F也沒關(guān)系,比如:
package net.rubyeye.javaprogramming.generic;
class AnotherSingleLinkQueue<E> {
static class Cell<F> {
private Cell<F> next;
private F element;
public Cell(F element) {
this.element = element;
}
public Cell(F element, Cell<F> next) {
this.element = element;
this.next = next;
}
public F getElement() {
return element;
}
/*
rest of Cell methods as before
*/
}
protected Cell<E> head;
protected Cell<E> tail;
/*
rest of SingleLinkQueue methods as before
*/
}
而一般的內(nèi)部類就不一樣了,內(nèi)部類可以直接使用外部類的類型參數(shù)甚至隱藏。
二、子類型與通配符
今天讀了第2節(jié),泛型的使用比我原先所知的更為復(fù)雜,java語法本來以簡潔優(yōu)美著稱,隨著java5,java7的到來,語法是越來越復(fù)雜,甚至可以說丑陋!-_-
要知道一點,比如List<Integer>不是List<Number>的子類,而是Collection<Integer>的子類。因為List<Integer>和List<Number>的類型是一樣的,都是List。那么如何表示參數(shù)化類型是Number的子類呢?這就需要用到通配符:
List<? extends Number>
表示類型變量是Number或者Number的子類。這個就是所謂的上界通配符,同樣,如果要表示類型變量是Number或者Number的super type,可以使用下界通配符:
List<? super Number>
而通配符List<?>等價于:
List<? extends Object>
通配符只能用于變量、局部變量、參數(shù)類型和返回類型,不能用于命名類和接口。比如下面的代碼將不能編譯通過:
class MyList implements List<?>{
//
}
通配符有另一個問題:因為通配符代表的是未知的類型,你不能在任何需要類型信息的地方使用它。比如下面的代碼同樣無法編譯通過:
SingleLinkQueue<?> strings =
new SingleLinkQueue<String>();
strings.add("Hello"); // INVALID: 無法編譯
SingleLinkQueue<? extends Number> numbers =
new SingleLinkQueue<Number>();
numbers.add(Integer.valueOf(25)); // INVALID: 無法編譯
三、泛型方法和類型推斷
如果我們想?yún)?shù)化方法的參數(shù)和返回值的類型,這就引出了泛型方法的聲明,聲明一個泛型方法的方式如下:
<T> T passThrough(T obj) {
return obj;
}
這個方法限制傳入的參數(shù)的類型與返回的參數(shù)類型將一致,可以看到,在方法簽名前加上<T>即可。我們可以這樣調(diào)用這個方法:
String s1 = "Hello";
String s2 = this.<String>passThrough(s1);
這樣的調(diào)用是不是比較奇怪?幸好提供了
類型推斷,根據(jù)參數(shù)的類型來自動判斷方法的類型(比如返回值類型),因此可以直接調(diào)用:
String s1 = "Hello";
String s2 = this.passThrough(s1);
如果方法有兩個類型變量,類型推斷將怎么處理呢?比如:
<T> T passThrough(T obj1,T obj2) {
return (T)(obj1.toString()+obj2.toString());
}
然后我們傳入兩個參數(shù),一個String,一個int,那么返回什么呢?
String s1="test";
String s3=this.passThrough(s1, 1); //編譯出錯
類型推斷是比較復(fù)雜的,這里將返回的將是Object類型,是傳入的參數(shù)類型的
交集