文章目錄
- 何為類型擦除
- 一探究竟
- 類型擦除規則
介紹類型擦除之前先看看以下代碼:
public static void genericRemove() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
List<Integer> list = new ArrayList<>();
list.add(1);
Method addMethod = list.getClass().getMethod("add", Object.class);
addMethod.invoke(list , "ff");
}
你覺得會執行成功嗎?
答案是會的。可能你會有疑問,list不是通過泛型限制成Integer類型了嗎,怎麼會執行成功呢。
泛型是在編譯期對類型進行安全檢查的,上面是通過反射設值,反射是在運作期起作用的,這就引出了類型擦除的概念了。
何為類型擦除
很多文章在提到類型擦除的時候說的都是泛型擦除,官網用的是Type Erasure直譯就是類型擦除。
在程式設計語言中,類型擦除是加載時過程,在程式運作時執行之前,通過該過程從程式中删除顯式類型注釋。 不需要程式伴随類型的操作語義稱為類型擦除語義,與類型傳遞語義形成對比。賦予類型擦除語義的可能性是一種抽象原則,確定程式的運作時執行不依賴于類型資訊。----維基百科
泛型被引入 Java 語言以在編譯時提供更嚴格的類型檢查并支援泛型程式設計。為了實作泛型,Java 編譯器将類型擦除應用于:
- 如果類型參數是無界的,則将泛型類型中的所有類型參數替換為其邊界或Object 。是以,生成的位元組碼隻包含普通的類、接口和方法。
- 必要時插入類型轉換以保持類型安全。
- 生成橋方法以保留擴充泛型類型中的多态性。
類型擦除確定不會為參數化類型建立新類;是以,泛型不會産生運作時開銷。
通俗的來說就是JVM不知道泛型的存在,在編譯成.class檔案後會把限定的泛型替換成object類或者是邊界類(比如父類、或者是幾個泛型共有的一個父類)。泛型是 jdk 1.5 出現的,為了相容jdk1.5之前的版本就搞了個類型擦除。
一探究竟
下面通過位元組碼的形式檢視類型擦除。
先定義一個泛型類,GenericClass
GenericClass.java
public class GenericClass<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
再建立一個測試類:
public class Test {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
GenericClass<String> stringGenericClass = new GenericClass<>();
stringGenericClass.setData("泛型類data");
System.out.println(stringGenericClass.getData());
}
}
檢視位元組碼:
Compiled from "Test.java"
public class generic.Test {
public generic.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
Code:
0: new #2 // class generic/GenericClass
3: dup
4: invokespecial #3 // Method generic/GenericClass."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 泛型類data
11: invokevirtual #5 // Method generic/GenericClass.setData:(Ljava/lang/Object;)V
14: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokevirtual #7 // Method generic/GenericClass.getData:()Ljava/lang/Object;
21: checkcast #8 // class java/lang/String
24: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
}
按理說GenericClass使用泛型限定類型為String,它的getter/setter方法應該是下面這個樣子的
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
但是通過位元組碼
第11行Method generic/GenericClass.setData:(Ljava/lang/Object;)V
第18行Method generic/GenericClass.getData:()Ljava/lang/Object;
可以看到GenericClass的getter/setter方法變成了下面這個樣子,JVM對泛型進行類型擦除,把String替換成了Object。
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
類型擦除規則
在類型擦除過程中,Java 編譯器擦除所有類型參數,如果類型參數是有界的,則将每個類型參數替換為其第一個邊界,如果類型參數是無界的,則将其替換為Object。
以下給出三個栗子來解釋這句話。
-
類型參數是無界的
還拿GenericClass< T>來說,類型T沒有顯示的繼承或者實作任何接口或者類,那麼在進行類型擦除的時候就會把T替換成Object類型。
-
類型參數有界
将GenericClass修改為如下形式:
public class GenericClass<T extends Number> {
private T data;
private GenericClass<T> next;
//省略getter方法
public void setNext(GenericClass<T> next) {
this.next = next;
}
public void setData(T data) {
this.data = data;
}
}
測試類:
public class Test {
public static void main(String[] args) {
GenericClass<Integer> stringGenericClass = new GenericClass<>();
stringGenericClass.setData(1);
stringGenericClass.setNext(new GenericClass<>());
System.out.println(stringGenericClass.getData());
}
}
再次檢視位元組碼:
Compiled from "Test.java"
public class generic.Test {
public generic.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class generic/GenericClass
3: dup
4: invokespecial #3 // Method generic/GenericClass."<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: invokevirtual #5 // Method generic/GenericClass.setData:(Ljava/lang/Number;)V
16: aload_1
17: new #2 // class generic/GenericClass
20: dup
21: invokespecial #3 // Method generic/GenericClass."<init>":()V
24: invokevirtual #6 // Method generic/GenericClass.setNext:(Lgeneric/GenericClass;)V
27: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: invokevirtual #8 // Method generic/GenericClass.getData:()Ljava/lang/Number;
34: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
37: return
}
從位元組碼可以看到
Method generic/GenericClass.setNext:(Lgeneric/GenericClass;)V
Method generic/GenericClass.setData:(Ljava/lang/Number;)V
T 被限定為隻能是Number的子類,是以在類型擦除的時候data屬性的類型被替換成了Number類型。
能力一般,水準有限,如有錯誤,請多指出。