泛型詳解

泛型

Java SE5 引入了泛型的概念,泛意爲廣泛,型是類型。所以泛型就是能廣泛適用的類型。

產生泛型的原因

​ 在JDK5.0之前,容器存儲的對象都只具有java的通用類型:Object。單根繼承結構意味着所有東西都是Object類型,所有該容器可以存儲任何東西,但是由於容器只存儲Object,所以當將對象引入容器時,它必須被向上轉型爲Object,因此它丟失了身份,將它取出時取到的是一個Object引用,必須將該Object向下轉型爲更具體的類型。這種轉型方式稱爲向下轉型。向上轉型是安全的,但是向下轉型是不安全的。

​ 那麼是否能創建一個容器,它知道自己所保存的對象類型,從而不需要向下轉型以及消除犯錯誤的可能。這樣的解決方案被稱爲參數化類型機制,參數化類型就是一個編譯器可以自動定製作用於特定類型上的類。這正是SE5.0 的重大變化之一,就是增加了參數化類型,在java中稱爲泛型。泛型的核心概念:告訴編譯器想使用什麼類型,編譯器幫你處理一切細節。

泛型的類型:泛型類,泛型方法,泛型接口

泛型類:使用<>擴住,放在類名後面,在創建這個類時在明確確定的對象

public class TestMethods<T> {
    private T t;

    public TestMethods(T tt) {
        t = tt;
    }

    public T get() {
        return t;
    }

    public static void main(String[] args) {
        TestMethods<String> tt = new TestMethods<>("s");
        String test = tt.get();
        //int test = tt.get(); 編譯報錯
    }

}

​ 如上例所示,泛型類中T的類型是在運行時(new 時)確定下來的,確定下來後,類中的T就被替換爲相應的類,所以會出現上例中編譯時報錯的情況,來在編譯時替程序員發現問題。

泛型方法:定義泛型方法,只需將泛型參數列表用<>擴住,放在返回值之前

public class TestMethods {

    public <T> void next(T t) {
        System.out.print(t.getClass().getName());
    }
    
    public static <T> void next(T t) { //正確寫法
        System.out.print(t.getClass().getName());
    }
}



public class TestMethods<T> {

    public static  void next(T t) { //這種寫法不存在會報錯
        System.out.print(t.getClass().getName());
    }
}

​ 泛型方法使得該方法能獨立於類而產生變化,所以使用泛型的原則是,無論何時,只要泛型方法能實現泛型類所需的功能,都要使用泛型方法,此外 static的方法(編譯時)因在編譯時不能確認泛型類(運行時)的類型參數,所以static方法想使用泛型能力一定是泛型方法。

泛型接口:與泛型類的使用方式無區別

public interface TestImplements<T> {
    T next();
}

public class TestMethods implements TestImplements<String> {
    @Override
    public String next() {
        return null;
    }

}

泛型的擦除

泛型信息只存在於代碼編譯階段,在進入 JVM 之前,與泛型相關的信息會被擦除掉,專業術語叫做類型擦除在泛型代碼的內部,無法獲得任何有關泛型參數類型的信息

爲什麼會存在擦除

爲了遷移兼容性 :java選擇泛型的擦除是一種中庸之道。如果重新設計java未必還會這樣做。

​ 爲了減少潛在的關於 擦除 的混亂,你必須認識到這不是一個語言的特徵,它是java泛型實現中的一種折中。因爲泛型不是java語言出現時就有的組成成分,所以這種折中是必須的

​ 如果泛型是java1.0就已經是其一部分了,那麼這個特徵將不會使用擦除來是實現–它將使用具體化,使類型參數保持爲第一類實體,這樣的話你就可以在類型參數上執行基於類型語言的操作和反射操作。擦除 減少了泛型的泛化性,泛型咋java中仍然是有用的,只是不如它們本來設想的那麼有用,其原因就是擦除

​ 在基於擦除的實現中,泛型類型被當作第二類類型來處理,既不能在重要的上下文環境中使用 類型。泛型類型只有在 靜態類型 檢查期間纔出現,在此之後,程序中所有的泛型類型都被擦除了,替換爲它們的非泛型上界。例如:諸如List這樣的類型註解將被擦除爲List,而普通的類型變量在未指定邊界的情況下將被擦除爲Object

​ 擦除的核心動機是它使得泛化的客戶端可以用非泛化的類庫來使用,反之亦然。這經常被稱爲"遷移兼容性"。因爲在理想情況下,當所有的事物都可以使用泛化,那我們就可以專注於此。但是在現實中,5.0之前是沒有泛型的,必須要處理這些沒有被泛化的類庫

​ 因此java泛型不僅必須支持向後兼容性,即現有的代碼是合法的,並且繼續保持之前的含義。而且還要支持遷移兼容性,使得舊的代碼不會影響新的代碼。一句話就是允許非泛型代碼與泛型代碼共存,擦除 使得這種向着泛型的遷移成爲可能

擦除的問題

​ 擦除的主要的正當理由是從非泛化代碼到泛化代碼的轉變過程,以及在不破環現有類庫的情況下,將泛型融入java語言。擦除使得現有的非泛型客戶端代碼能夠在不改變的情況繼續使用直到客戶端準備好用泛型重寫這些代碼。這是一個崇高的動機,因爲他不會突然間破環所有現有的代碼

​ 擦除的代價是顯著的,泛型不能用於顯式地引用運行時類型的操作之中。因爲所有關於參數的類型信息都丟失了。

​ 無論何時,當你在編寫泛型代碼的時候,必須時刻提醒自己,你只是看起來 好像擁有了 有關類型的信息。其實它只不過是一個Object而已

​ 泛型不是強制的

​ 當你希望將類型參數不要僅僅當作是Obbject處理時,就需要付出額外努力來管理邊界(例如通過編寫這些來管理)

​ 其他編程語言的參數化類型相比(例如C++):通常比java得心應手,它們的參數化類型機制比java的更靈活,更強大

通配符

通配符 : 除了用 <T>表示泛型外,還有 <?>這種形式。 被稱爲通配符。通配符的引用的目的在於明確類型

上界 <? extends Fruit>

下界 <? super Apple>

無界 <?>

邊界正是因爲上述擦除的原因,在運行階段,類型信息被擦除,在默認情況下將T識別爲Object 。所以纔有了通配符的機制,通過extends 關鍵字,可以使T有一個更小的邊界,識別爲更具象的對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章