1、泛型的類(lèi)型擦除
Java的泛型是偽泛型,不同于C++的模板機(jī)制,這是因?yàn)镴ava的泛型只存在編譯期間,在編譯完成后泛型就會(huì)被擦除。引入泛型是為了將類(lèi)型檢查提前到編譯期間,將類(lèi)型轉(zhuǎn)換交由編譯器處理,那么為什么還要進(jìn)行泛型的擦除呢?泛型擦除的目的是為了向下兼容老的Java版本,老的Java版本是沒(méi)有泛型概念的。
下面通過(guò)一個(gè)例子證明泛型的擦除
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
}
}
在這個(gè)例子中,我們定義了兩個(gè)ArrayList數(shù)組,一個(gè)是ArrayList<String>泛型類(lèi)型的,只能存儲(chǔ)字符串;一個(gè)是ArrayList<Integer>泛型類(lèi)型的,只能存儲(chǔ)整數(shù),最后,我們通過(guò)list1對(duì)象和list2對(duì)象的getClass()方法獲取他們的類(lèi)的信息,最后發(fā)現(xiàn)結(jié)果為true。說(shuō)明泛型類(lèi)型String和Integer都被擦除掉了,只剩下原始類(lèi)型,在運(yùn)行時(shí)ArrayList<String>和ArrayList<Integer>對(duì)應(yīng)的class都是ArrayList.class。
既然泛型的類(lèi)型在編譯完成后就會(huì)被擦除,這樣一來(lái)我們是不是就可以在運(yùn)行時(shí)向ArrayList<Integer>中添加字符串呢?當(dāng)然可以,代碼如下:
public class Test {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1); //這樣調(diào)用 add 方法只能存儲(chǔ)整形,因?yàn)榉盒皖?lèi)型的實(shí)例為 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "asd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
在程序中定義了一個(gè)ArrayList泛型類(lèi)型實(shí)例化為Integer對(duì)象,如果直接調(diào)用add()方法,那么只能存儲(chǔ)整數(shù)數(shù)據(jù),不過(guò)當(dāng)我們利用反射調(diào)用add()方法的時(shí)候,卻可以存儲(chǔ)字符串,這說(shuō)明了Integer泛型實(shí)例在編譯之后被擦除掉了,只保留了原始類(lèi)型。
2、類(lèi)型擦除后保留的原始類(lèi)型
原始類(lèi)型就是擦除去了泛型信息,最后在字節(jié)碼中的類(lèi)型變量的真正類(lèi)型。如果泛型無(wú)限定,則會(huì)用Object替換,如果泛型有限定,則會(huì)用限定類(lèi)型替換。
2.1、無(wú)限定的泛型擦除后替換成Object
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
上面泛型T是無(wú)限定的,所以在編譯完成后就被替換成Object,替換后等同于下面的代碼:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
2.1、有限定的泛型擦除后替換成第一個(gè)邊界類(lèi)型
class Pair<T extends Number & Comparable> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
上面代碼中泛型T的上邊界為Number和Comparable,泛型擦除會(huì)被第一個(gè)邊界類(lèi)型替換,替換后的代碼如下:
class Pair {
private Number value;
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
}
3、泛型擦除后引起的問(wèn)題
3.1、泛型擦除后如何保證只能使用泛型限定的類(lèi)型
ArrayList<String> list = new ArrayList<String>();
list.add("123");
list.add(123);//編譯錯(cuò)誤
ArrayList<String>泛型擦除會(huì)導(dǎo)致String被替換成Object,為什么只能向list中添加字符串呢?
因?yàn)樵诜盒筒脸?,編譯器會(huì)先進(jìn)行類(lèi)型檢查,然后再擦除,再進(jìn)行編譯。
3.2、泛型的類(lèi)型擦除前,類(lèi)型檢查的原理
先看如下代碼
ArrayList<String> list1 = new ArrayList();
list1.add("Hello");//編譯成功
list1.add(1);//編譯失敗
ArrayList list2 = new ArrayList<String>();
list2.add("Hello");//編譯成功
list2.add(1);//編譯成功
從上面代碼可以看出:ArrayList list2 = new ArrayList<String>()的泛型的類(lèi)型檢查是不成功的,我們依然可以向list2中添加任意數(shù)據(jù)。這又是為什么呢?
new ArrayList()只是開(kāi)辟了一個(gè)內(nèi)存空間,可以存儲(chǔ)任何類(lèi)型的對(duì)象,而類(lèi)型檢查是針對(duì)它的引用,引用list2并沒(méi)有使用泛型,所以并不能實(shí)現(xiàn)類(lèi)型檢查的功能。
例子:
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList();
list1.add("1"); //編譯通過(guò)
list1.add(1); //編譯錯(cuò)誤
String str1 = list1.get(0); //返回類(lèi)型就是String
ArrayList list2 = new ArrayList<String>();
list2.add("1"); //編譯通過(guò)
list2.add(1); //編譯通過(guò)
Object object = list2.get(0); //返回類(lèi)型就是Object
new ArrayList<String>().add("11"); //編譯通過(guò)
new ArrayList<String>().add(22); //編譯錯(cuò)誤
String str2 = new ArrayList<String>().get(0); //返回類(lèi)型就是String
}
}
從上面代碼可以看出類(lèi)型檢查是針對(duì)引用的,誰(shuí)是一個(gè)引用,用這個(gè)引用調(diào)用泛型方法時(shí),就會(huì)對(duì)這個(gè)引用調(diào)用的方法進(jìn)行類(lèi)型檢查,而無(wú)關(guān)它真正引用的對(duì)象。
3.3、泛型的類(lèi)型轉(zhuǎn)換
泛型擦除會(huì)導(dǎo)致泛型類(lèi)型被替換成Object或者上邊界類(lèi)型,那么為什么在獲取值時(shí)并不需要進(jìn)行強(qiáng)轉(zhuǎn)呢?
看下面的例子:
public class Main2<T> {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
// list.get(0); //語(yǔ)句1
String str = list.get(0);
}
}
泛型擦除會(huì)將泛型String替換成Object,所以在調(diào)用list.get()時(shí)返回的泛型擦除后的Object,那為什么String str = list.get(0);不需要進(jìn)行強(qiáng)轉(zhuǎn),這是因?yàn)榉祷厍皟?nèi)部已經(jīng)進(jìn)行了轉(zhuǎn)換。
3.4、類(lèi)型擦除和多態(tài)的沖突
先看個(gè)例子:定義一個(gè)泛型類(lèi)Parent
class Parent<T>{
private T t;
public void setValue(T t){
this.t=t;
}
public T getValue(){
return t;
}
}
定義一個(gè)Child類(lèi)實(shí)現(xiàn)Parent,并將泛型的具體類(lèi)型指定為String類(lèi)型。
class Child extends Parent<String> {
@Override
public void setValue(String first){
super.setValue(first);
}
@Override
public String getValue(){
return super.getValue();
}
}
泛型擦除會(huì)導(dǎo)致Parent的泛型被替換成Object,所以子類(lèi)繼承Parent時(shí)重寫(xiě)父類(lèi)的方法應(yīng)該為
class Child extends Parent<String> {
@Override
public void setValue(Object first){
super.setValue(first);
}
@Override
public Object getValue(){
return super.getValue();
}
}
可是子類(lèi)中重寫(xiě)父類(lèi)兩個(gè)方法的具體實(shí)現(xiàn)確實(shí)下面這樣的:
@Override
public void setValue(String first){
super.setValue(first);
}
@Override
public String getValue(){
return super.getValue();
}
可見(jiàn)類(lèi)型擦除和多態(tài)產(chǎn)生了沖突,為了解決這個(gè)沖突Java編譯器使用了橋方法。通過(guò)指令javap -c -s Child.class查看字節(jié)碼文件反編譯的結(jié)果:

可以看到子類(lèi)中新增了兩個(gè)橋方法:
public void setValue(Object first) {
setFirst((String)first);
}
public Object getValue() {
//這里返回的 String 類(lèi)型的 getFirst 方法
return getFirst();
}
這兩個(gè)橋方法,相當(dāng)于重寫(xiě)了父類(lèi)的兩個(gè)方法,最終還是會(huì)調(diào)用子類(lèi)的方法,相當(dāng)于實(shí)現(xiàn)了子類(lèi)對(duì)父類(lèi)的重寫(xiě)。
橋方法為子類(lèi)和父類(lèi)之間架起了一座連通的橋梁,真正實(shí)現(xiàn)了泛型繼承中的動(dòng)態(tài)綁定,也很好的解決了類(lèi)型擦除與多態(tài)之間的沖突。