其實早在1999年的JSR 14規范中就提到了泛型概念,知道jdk5泛型的使用才正式發布,在jdk7后,又對泛型做了優化,泛型的推斷.
泛型類
public class Pair<T> { private T first; private T second; public Pair() { first = null; second = null; } public Pair(T first, T second) { this.second = second; this.first = first; } public void setFirst(T newValue) { first = newValue; } public void setSecond(T newValue) { second = newValue; } }
怎么理解泛型類的定義.首先在類名后根上<T>這個T就是任意類型.在 Java 庫中, 使用變量 E 表示集合的元素類型, K 和 V 分別表示表的關鍵字與值的類型。T ( 需要時還可以用臨近的字母 U 和 S) 表示“ 任意類型”。然后在類中的成員,都可以使用這個T,你既可以把T當做參數,也可以把T當做返回值.也可以把T當做成員變量的類型.這個T到底存儲的什么類型,取決於你在實例化Pair時指定的具體類型.但是以上寫法,你一旦指定了一個實際類型,那么這個類中所有的T都會是同一個類型.
public class Main { public static void main(String[] args) { Pair<String> pair = new Pair<>(); pair.setFirst("第一"); pair.setSecond("第二"); } }
你也可以在一個泛型類上定義多個泛型
public class Pair<T,U> { private T first; private U second; public Pair() { first = null; second = null; } public Pair(T first, U second) { this.second = second; this.first = first; } public void setFirst(T newValue) { first = newValue; } public void setSecond(U newValue) { second = newValue; } }
但是你需要記得,因為泛型的作用域在類級別.一下寫法是錯誤的.
你在類上定義了個T表示,你所實例化的每一個類都要指定一個類型,現在,現在你試圖不做類的實例化,而直接使用T,那么這個T你要從哪里定義呢?記住要使用泛型,先確定泛型的具體類型.
泛型方法
public class Demo3 { public <T> void show(T t){ System.out.println(t.toString()); } public static <S> void show2(S s){ System.out.println(s); }
public class Main { public static void main(String[] args) { Demo3 demo3 = new Demo3(); demo3.<String>show("a"); demo3.show("a"); Demo3.show2(1); } }
你在一個方法上指定了泛型,即泛型的作用域在方法體上,也就是說,你每次調用方法都要指定具體的類型.當然你不用每次調用都使用<T>語法,因為jdk7的泛型推斷.編譯器自然可以通過你的實參而推斷出你想要的實際類型.將泛型定義到方法上,泛型的T可以用到參數,方法體,返回值.
當然在你指定泛型時,也可以有以下寫法
public class Demo1 { public <String> void add(String t){ } }
不過這通常是沒有任何意義的,否則,你要想表達什么呢?定義了一個泛型方法,並且限定泛型的實際類型是String?
泛型的擦除
泛型的擦除可謂是泛型中的重中之重了.字面意思,泛型類型會被擦除.引起兩個問題:1在什么情況下擦除2被擦除后的的類什么樣.
文檔說明泛型只在編譯器用來檢測類型,編譯時即會擦除泛型.所以泛型是在編譯時被擦除的.下面看一下代碼
public class Demo4<T> { private T type; public void add(T t) { } }
可以看出在無限定類型時(沒有使用extends 或 super 限定泛型)在編譯后原來的T被替換成了Object.
泛型表達式
看以下代碼
public class Pair<T> { private T first; private T second; public Pair() { first = null; second = null; } public Pair(T first, T second) { this.second = second; this.first = first; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } }
public class Main { public static void main(String[] args) { Pair<Student> pair = new Pair<>(); Student s = pair.getFirst(); } }
前邊已經說過,對於無限定類型,在編譯時會擦掉泛型的而變成object.那么以上這個Main中運行的代碼,pair通過get()方法的返回值確可以直接賦值給Student這又是怎么回事呢?
這是兩個class反編譯后的.可以看到,在get()方法后,編譯器幫我們自動做了類型轉換
泛型與多態的沖突(橋方法)
public class Pair<T> { private T first; private T second; public Pair() { first = null; second = null; } public Pair(T first, T second) { this.second = second; this.first = first; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } }
public class PairChild extends Pair<Person> { @Override public void setFirst(Person first) { super.setFirst(first); } }
我們繼承了Pair並且指定了他的具體類型.那么我們覆蓋Pair方法時就只能傳入Person類型的參數了.那么,在已經編譯好的Pair.class中,setFirst()應該還是Object類型.這時我們到底算是覆蓋父類的方法了嗎?
Pair.class
PairChild.class
其中object參數的方法就是橋方法,當我們使用setFirst時,會先調用這個橋方法,這個橋方法,會將object強制轉換成Person然后在調用PairChild自己的setFirst().
打破泛型的約束
有時間泛型對待數據類型也不是絕對安全的.請看一下示例
public class Pair<T> { private T first; private T second; public Pair() { first = null; second = null; } public Pair(T first, T second) { this.second = second; this.first = first; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } }
public class PairSort { public void sort(Pair pair){ pair.setFirst("可是我是字符串"); } }
public class Main { public static void main(String[] args) { PairSort pairSort = new PairSort(); Pair<Person> pair = new Pair(); pairSort.sort(pair); Person first = pair.getFirst(); } }
我們有一個PairSort類,這個類接受一個Pair實例,但是並沒有指定泛型,也就是說,它現在接受的是一個Object.然后給他的卻是一個指定了Person類型的Pair.這時我們在調用Pair就獲得了一個錯誤.
約束與限制
1 不能使用基本類型實例化類型參數,我們不能傳遞基本數據類型當做泛型的具體類型,原因是當泛型擦除后需要轉換成具體的object子類.而基本數據類型,並不能轉換成object
2 檢查一個對象的類型不能帶泛型參數.看一下代碼
3 不能創建參數化類型的數組
4 不能實例化類型變量
5 盡管有泛型的擦除,但是在靜態中依然不能使用泛型
6 泛型類中不能覆蓋父類的方法
通配符
請看以下錯誤代碼
public class Pair<T> { private T first; private T second; public Pair() { first = null; second = null; } public Pair(T first, T second) { this.second = second; this.first = first; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } }
public class Employee { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Manager extends Employee { }
public class Main { public static void main(String[] args) { Pair<Manager> pair = new Pair<>(); printBuddies(pair);//錯誤的 } public static void printBuddies(Pair<Employee> p) { Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName()); } }
printBuddies()需要一個Pair類型參數,我們指定Pair的泛型類型是Employee.當我們傳遞實參時,傳遞的是Pair<Manager>類型的實參,這是不正確的.Pair<Employee>和Pair<Manager>沒有父子關系.
他們都只是Pair類型.那么對於這種問題有沒有解決方案的?
public class Main { public static void main(String[] args) { Pair<Manager> pair = new Pair<>(); printBuddies(pair); Pair<Employee> employeePair = new Pair<>(); printBuddies(employeePair); } public static void printBuddies(Pair<? extends Employee> p) { Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName()); } }
使用 ? extends Employee 指定Pair的泛型類型是Employee或者是其子類.這樣就不會出現編譯異常
看一下對通配符的使用
public class Main { public static void main(String[] args) { Pair<Manager> pair = new Pair<>(); Pair<? extends Employee> pair1 = pair; pair.setFirst(new Employee());//錯誤的 pair.setFirst(new Manager()); Employee first = pair.getFirst(); } }
我們創建一個Manager類型的Pair.將他賦值給Pair<Employee>這是沒錯的.但是當我們調用set方法則會出現編譯異常.原因是編譯器知道我們要傳入Employee的子類型但是不知道具體傳入的是哪個子類型所以
拒絕編譯.但是get方法則沒有問題,因為返回是一個Employee
超類通配符
public class Main { public static void main(String[] args) { printBuddies(new Pair<Employee>()); printBuddies(new Pair<Manager>()); } public static void printBuddies(Pair<? super Manager> p) { Object first = p.getFirst(); Object second = p.getSecond(); } }
我們限定printBuddies的參數為Manager或者其父類.
由於我們希望傳入Manager或者其父類所以get方法拒絕我們用一個Manager接收.它無法確定我們到底傳入的是Manager還是其父類所以只能用Object接收.
無限定通配符
雖是無限定通配符,但是他的使用限定是最大的.我們甚至無法使用set方法.除非傳遞一個null.Object都不行.在get方法時我們也只能用Object來接收.那么為什么要有這樣一個雞肋的通配符呢?