java-泛型

使用泛型可以在編譯時而不是在運行時檢測出錯誤,在試圖使用一個不相容的對象時,編譯器就會檢測出這個錯誤。提高了可靠性和可讀性。

泛型類型必須是引用類型,不能用基本類型來替代泛型類型。


一、泛型類和接口

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);
    }  

}  
發佈了21 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章