使用泛型可以在編譯時而不是在運行時檢測出錯誤,在試圖使用一個不相容的對象時,編譯器就會檢測出這個錯誤。提高了可靠性和可讀性。
泛型類型必須是引用類型,不能用基本類型來替代泛型類型。
一、泛型類和接口
class MyStack<E>{
public MyStack() {
//構造方法應該這樣定義
}
}
創建時
MyStack<String> stack1 = new MyStack<String>();
可以定義一個類或接口個作爲泛型或者接口的子類型
String類實現Comparable接口
public class String implements Comparable<String>
二、泛型方法
public class Test {
public static void main(String[] args){
String[] str1 = {"a", "b", "c"};
Integer[] integer1 = {1, 2, 3};
Test.<String>print(str1);
Test.<Integer>print(integer1);
}
public static <E> void print(E[] list){
for (int i = 0; i < list.length; i++) {
System.out.print(list[i] + " ");
}
System.out.println();
}
}
方法調用中可以省略掉類型參數,寫成
Test.print(str1);
可以定義泛型類型爲<T extends MyType>
這樣的泛型類型稱爲受限的
三、如何理解<T extends Comparable<T>>
和<T extends Comparable<? super T>>
首先理解一下<T extends Comparable<T>>
類型 T 必須實現 Comparable 接口,並且這個接口的類型是 T。只有這樣,T 的實例之間才能相互比較。例如,在實際調用時若使用的具體類是 Food,那麼 Food 必須implements Comparable<Food>
再說<T extends Comparable<? super T>>
類型 T 必須實現 Comparable 接口,並且這個接口的類型是 T 或 T 的任一父類。T 的實例之間,T 的實例和它的父類的實例之間,可以相互比較大小。例如,在實際調用時若使用的具體類是 Fruit (假設 Fruit 有一個父類 Food),Fruit 可以從Food 那裏繼承 Comparable<food>
,或者自己 implements Comparable<Fruit>
四、?非受限通配
和? extends Object是相同的, 表示任何一種對象類型
public class Test {
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("hello");
strList.add("hi");
print(strList);
}
public static <E> void print(List<?> list){
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
}
}
不能使用List<Object>
代替List<?>
因爲List<String>
並不是List<Object>
的實例
五、上界<? extends Class>
和下界<? super Class>
get-put principle:
Use an extends wildcard when you only get values out of a structure.
Use a super wildcard when you only put values into a structure.
And don't use a wildcard when you both want to get and put from/to a structure.
Exceptions are:
You cannot put anything into a type declared with an extends wildcard except for the value null, which belongs to every reference type.
You cannot get anything out from a type declared with an super wildcard except for a value of type Object, which is a super type of every reference
PECS原則:
如果要從集合中讀取類型T的數據,並且不能寫入,可以使用 ? extends 通配符;(Producer Extends)
如果要從集合中寫入類型T的數據,並且不需要讀取,可以使用 ? super 通配符;(Consumer Super)
如果既要存又要取,那麼就不要使用任何通配符。
原問題連接:http://stackoverflow.com/questions/1292109/explanation-of-the-get-put-principle
大概意思爲
class Fruit{}
class Apple extends Fruit{}
class Banana extends Fruit{}
public class Test {
public static void main(String[] args){
List<? extends Fruit> list = new ArrayList<>();
//list.add(new Apple());
//list.add(new Banana());
list.add(null);
list.contains(new Apple());
list.indexOf(new Apple());
}
}
不能向其中添加Apple或Banana的實例
你知道那是fruit的集合,但是不知道是那種特定的。可以取出,例如list.contains()和list.indexOf(),你知道取出的將會是fruit。但是不能添加進去,編譯器並不能瞭解這裏到底需要哪種 Fruit 的子類型
但是可以add(null),因爲 null 代表任何類型。
class Fruit{}
class Banana extends Fruit{}
class YellowBanana extends Banana{}
public class Test {
public static void main(String[] args){
List<? super Banana> list = new ArrayList<>();
list.add(new Banana());
list.add(new YellowBanana());
//list.add(new Fruit());
//Banana banana = list.get(0);
Object banana = list.get(0);
}
}
現在是List<? super Banana>
,其中都是具有Banana的父類的列表,可以添加進Banana的實例或者Banana的子類YellowBanana。但是不能add(new Fruit()), 因爲?代表Banana的父類,但是編譯器不知道你要添加哪種Banana的父類,因此不能安全地添加。
當取出的時候,你不知道將會得到什麼(可能不會是個Banana),所以不能,可以確定它是一個Object。因爲編譯器不能確定列表中的是Banana的哪個子類,所以只能返回 Object。
舉個使用例子
public class Test {
public static void main(String[] args){
List<String> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
list1.add("hello");
list1.add("world");
list2.add(1);
add(list1, list2);
print(list2);
}
public static <T> void add(List<T> list1, List<? super T> list2){
for (int i = 0; i < list1.size(); i++) {
list2.add(list1.get(i));
}
}
public static void print(List<Object> list){
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
輸出:
1
hello
world
這裏<? super T>
表示類型T或T的父類型。Object是String的父類型
泛型類型和通配類型之間的關係(無視水印orz)
六、擦除
泛型是使用一種稱爲類型擦除的方法實現的,編譯器使用泛型類型信息來編譯代碼,但是隨後消除它,因此泛型信息在運行時是不可用的,這種方法使泛型代碼向後兼容使用原始類型的遺留代碼
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2);
結果爲:true
這是java泛型擦除造成的,ArrayList<Integer>()
和new ArrayList<String>()
,在編譯器中都會轉換成原始類型ArrayList。泛型存在於編譯時,一旦編譯器確認泛型類型是安全使用的,就會將它替換成原始類型
在泛型代碼內部,無法獲得任何有關泛型參數類型的信息。
類型擦除的原則
1、所有參數化容器類都被擦除成非參數化,如List<E>
擦除成Lsit
2、參數化的數組被擦除成非參數化的數組,如List<E>[]
擦除成List[]
3、參數類型E,沒有上限的情況下,擦除成Object
4、所有約束參數,如<? extends E>
擦除成E
5、多個約束的情況下,擦除成第一個,如<T extends Object & E>
擦除成Object
七、擦除產生的問題
1、不管實際的具體類型是什麼,泛型類是被它的所有實例所共享的。在編譯時ArrayList<Integer>
和ArrayList<String>
是兩種不同的類型,但是運行時只有一個ArrayList會被加載。
所以要求所有靜態成員不能夠是泛型
2、重載衝突
public void method(T t){
\\do something
}
public void method(Object t){
\\do something
}
報錯:Method method(Object) has the same erasure method(Object) as another method in type Test1
是因爲參數T被擦除成了Object,導致這兩個方法的特徵簽名變得一模一樣
3、接口衝突
class Test2 implements Comparable<String>, Comparable<Integer>{
//錯誤的
}
這裏Comparable<String>, Comparable<Integer>
都是泛型接口,被擦除成Comparable。
如果要實現多個泛型接口,只能實現具有不同擦除效果的接口
八、擦除對泛型的限制
由於泛型類型在運行時被擦除, 對於如何使用泛型類型有一些限制
1、if(obj instanceof T);
2、不能使用new E()
例如
運行時泛型類型是不可用的
3、不能使用new E[]
4、在靜態的環境下不允許類的參數是泛型
5、異常類不能是泛型
假如定義一個泛型的異常類
class MyException<T> extends Exception{
}
捕獲異常的時候
因爲運行時類型信息是不出現的
解除擦除帶來的問題
1.解決instanceof
先理解一下什麼是Class<T>
使用泛型, Class.newInstance() 方法具有一個更加特定的返回類型:
class Class<T> {
T newInstance();
}
T指的是由此 Class 對象建模的類的類型。例如,Integer.class 的類型是 Class<Integer>
。如果將被建模的類未知,則使用 Class<?>
。
創建實例:調用方法 Class.forName() 或者使用類常量X.class。Class.forName() 被定義爲返回 Class。另一方面,類常量 X.class 被定義爲具有類型 Class,所以 String.class 是Class 類型的。
現在來說明如何利用Class<T>
解決instanceof的問題
Class<T> type;
if ( type.isInstance(obj) ) {
//...
}
原問題連接:http://stackoverflow.com/questions/5734720/java-generics-obj-instanceof-t
舉個例子:
class A{}
class B extends A{}
public class Test<T> {
private Class<T> type;
public Test(Class<T> type){
this.type = type;
}
public boolean compareTo(Object obj){
return type.isInstance(obj);
}
public static void main(String[] args) {
Test<A> test = new Test<A>(A.class);
System.out.println(test.compareTo(new A()));
System.out.println(test.compareTo(new B()));
}
}
結果:
true
true
二、解決泛型數組
最簡單的方式是通過Array.newInstance(Class<t>type,int size)
的方式來創建數組。
public class Test<T> {
private Class<T> type;
public Test(Class<T> type) {
this.type = type;
}
@SuppressWarnings("unchecked")
T[] createArray(int size) {
return (T[]) Array.newInstance(type, size);
}
public static void main(String[] args) {
Test<String> test = new Test<String>(String.class);
String[] list = test.createArray(10);
}
}