這裡想說一下在集合框架前需要理解的小知識點,也是個人的膚淺理解,不知道理解的正不正確,請大家多多指教。
這裡必須談一下java的泛型,因為它們聯系緊密,我們先看一下這幾行代碼:
Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println( c1 + "==" + c2 + " is " + (c1 == c2) );
這裡主要想測試一下這兩個類是不是相等的,根據我之前的認識,這應該是不相等的,但是運行輸出的結果是:
class java.util.ArrayList==class java.util.ArrayList is true
根據打印結果顯然c1 和 c2都叫class java.util.ArrayList ,所以後面是否相等跟的是 true,居然和我的預期完全相反的,
這是怎麼回事呢,這就要提到java泛型裡的類型擦除(type erasue),這黑科技的提出肯定是有多方面原因的,我摘錄了一些我了解到的原因。
讓我們一步步了解:
一、保持代碼版本的兼容性
在Java SE 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實現參數的"任意化","任意化"帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對於強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運行的時候才出現異常,這是一個安全隱患。所以後期引入了泛型,泛型的好處是在編譯的時候檢查類型安全,並且所有的強制轉換都是自動和隱式的,提高代碼的重用率。但是需要保持代碼的兼容性
二、目前了解到通常情況下編譯器處理泛型有兩種方式,java選擇了類型擦除,兩種方式如下:
1.Code specialization。在實例化一個泛型類或泛型方法時都產生一份新的目標代碼(字節碼or二進制代碼)。例如,針對一個泛型list,可能需要 針對string,integer,float產生三份目標代碼。
2.Code sharing。對每個泛型類只生成唯一的一份目標代碼;該泛型類的所有實例都映射到這份目標代碼上,在需要的時候執行類型檢查和類型轉換。
C++中的模板(template)是典型的Code specialization實現。C++編譯器會為每一個泛型類實例生成一份執行代碼。執行代碼中integer list和string list是兩種不同的類型。這樣會導致代碼膨脹(code bloat),
Code specialization另外一個弊端是在引用類型系統中,浪費空間,因為引用類型集合中元素本質上都是一個指針。沒必要為每個類型都產生一份執行代碼。而這也是Java編譯器中采用Code sharing方式處理泛型的主要原因。
Java編譯器通過Code sharing方式為每個泛型類型創建唯一的字節碼表示,並且將該泛型類型的實例都映射到這個唯一的字節碼表示上。將多種泛型類形實例映射到唯一的字節碼表示是通過類型擦除(type erasue)實現的。
類型擦除的來世大概說了,我們看看類型擦除大概是怎麼回事:
我們都知道重載,就是在類中可以創建多個方法,它們具有相同的名字,但具有不同的參數和不同的定義,根據java編輯器處理泛型的方式,上面代碼編譯完成後應該會變成這樣:
Class c1 = new ArrayList<Object>().getClass(); Class c2 = new ArrayList<Object>().getClass(); System.out.println( c1 + "==" + c2 + " is " + (c1 == c2) );
可以看出來,無論類是什麼,經過編譯器後再虛擬機裡運行都是相同的類,所以結果是相等的。
所以總結一下類型擦除引起一些奇怪的現象,包括: