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