在 Java 中的 泛型 ,常常被稱之為 偽泛型 ,究其原因是因為在實際代碼的運行中,將實際類型參數的信息擦除掉了 (Type Erasure) 。那是什麼原因導致了 Java 做出這種妥協的呢?下面我就帶著大家以 Java 語言設計者的角度,帶領大家一起了解這裡面的辛酸過往。
在了解 Java "偽泛型" 之前,我們先簡單講一講"真泛型"與「偽泛型」的區別。
一般程式語言引入泛型的思路,都是通過 編譯時膨脹法 。
以 Java 語言實現"真泛型"為例,首先,對 泛型類型(泛型類、泛型接口) 、 泛型方法 的名字使用特別的編碼,例如將 Factory
術語中文含義舉例
Parameterized type參數化類型List
Actual type parameter實際類型參數String
Formal type parameter形式類型參數E
以 Factory
//替換前的代碼
Factory() f1 = new Factory();
Factory() f2 = new Factory();
//替換後的代碼
Factory() f1 = new Factory@String@()
Factory() f2 = new Factory@Integer@();
因為含有不同的 實際類型參數 的 泛型類型 都被替換為了不同的類,且泛型類型中的類型也都得到了確認。所以我們在程序中可以這麼做:
class Factory {
T data;
public static void f(Object arg){
if(arg instanceof T){...}//success
T var = new T();//success
T[] array = new T[10];//success
}
}
單從技術來說,Java 是完全 100% 能實現我們所說的 」真泛型」。想要實現真泛型,有如下兩件事Java 必須要處理:
就拿 ArrayList 來說,也就是必須這麼做:
ArrayList
即使以上的事情都做了,Java 也並不能採用這種方案。試想如下情況:
如果我有一個 Java 5 之下 的 A 項目與第三方的 B1 lib,其中有 A 項目中引用了 B1 lib 中的某個 ArrayList ,隨著 Java 的升級,B1 lib 的開發者為了使用 Java 新特性--泛型,故將代碼遷移到了 Java 5,並重新生成了 B2 lib,那麼 A 項目要兼容 B2 lib,那麼 A 項目中必須升級到 Java 5 並同時修改代碼。
A 項目為什麼必須要升級 Java 5?
在 Java 中不支持高版本的 Java 編譯生成的 class 文件在低版本的 JRE 上運行,如果嘗試這麼做,就會得到 UnsupportedClassVersionError 錯誤。如下圖所示:
故 A 項目要適配 B2 lib,必要要把 Java 升級到 Java 5。
那現在我們再回過頭來想想,Java 版本疊代都從 1.0 到 5.0了,有多少的開源框架,有多少項目,如果為了引入泛型,強行讓開發者修改代碼。這種情況,各位同學。自行腦補。估計數以萬計的開發者拿著刀,在堵 Java 語言架構師的門吧。
在上節中,我們探討了 Java 不能直接引入「真泛型」 的實際原因。因為「真泛型」的引入,勢必會為原本不支持泛型的 API 平行添加一套泛型 API。而新增了API,對於 Java 開發者來說,又必須要做遷移。
那還有什麼方案,能讓開發者平滑的過渡到 Java 5, 又能使用泛型新特性呢?
有的,有的。Java 如果想擺脫用戶新版本的遷移問題。Java 必要要做以下兩件事情:
下面我們分別探討一下這兩件事做的目的及其原因。
做第一件事,是保證了開發者不會因為 Java 的升級,而對以前的老代碼進行修改,以 ArrayList 為例, 直接在原有包(java.util)下進行修改 ,也就是這樣:
//:point_down:Java老版本
class ArrayList{}
//:point_down:Java5泛型版本
class ArrayList{}
做第二件事的目的,還是以我們之前的例子進行分析,在 A 項目中,A 項目引用了 B1 lib 中的 ArrayList(用 list 變量記錄),那麼假設 A 項目升級到 Java 5 後,還是引用的 B1 lib,那麼必然會出現如下這種情況:
下述代碼中,A 項目將泛化後 ArrayList
ArrayList list = new ArrayList();
ArrayList
這種情況的出現,會導致一個問題。就是 b1 項目中的 ArrayList 是不知道 A 項目中的 Arraylist 已經泛型化了的,那麼如何保證 泛型化後 的 ArrayList( 也就是ArrayList
如果按照我們之前講解的 「真泛型」 思路來處理 Java 的泛型, 那麼 new ArrayList
ArrayList list = new Factory@String@()
複製代碼
從代碼邏輯上來看,根本就跑不通。因為 ArrayList 與 ArrayList@String@ 根本就不是同一類, 那怎麼辦呢?
最為直接的解決方案就是,不再為參數化類型創造新類了,同時在編譯期間將 泛型類型 中的 類型參數 全部替換 Object (因為不創建新類了,那麼在泛型類中的 T 對應的類型,只能用 Object 替換)。
在 Java 的泛型實際實現中,會根據泛型類型中的類型參數有無邊界,來選擇是否替換為邊界或 Object。
舉個例子:
在上述代碼中,聲明了一個泛型類型 Node
//編譯器的代碼
Node node = new Node();
//編譯後的代碼
Node node = new Node();
通過編譯器的」魔法「,Java 就解決了處理泛型兼容老版本的問題。
閱讀到這裡,我相信大家已經明白了 Java 中的泛型為什麼要擦除類型信息了。雖然 Java 的 "偽泛型「 一直被其他程式語言所歧視,但不管怎樣,兼容老版本這種行為,也是一件值得尊敬以及認可的一件事。
不管 Java 做了什麼,總是功大於過的。這裡推薦一個視頻給大家。相信看了這個視頻之後,大家會知道 Java 對於整個世界的重要性。