參考
The Java? Tutorials
http://www.baeldung.com/java-type-erasure
泛型擦除
- Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
如果泛型參數(shù)是有界的, 那么編譯之后會(huì)替換成邊界, 否則的話會(huì)替換成Object. 插入必要的轉(zhuǎn)型操作. 對(duì)于繼承泛型類(lèi)的類(lèi)來(lái)說(shuō), 編譯器還會(huì)添加橋接方法bridge methods以保持多態(tài). 因?yàn)榫幾g過(guò)后并沒(méi)生成新的類(lèi), 所以泛型運(yùn)行時(shí)并沒(méi)有引入額外的花銷(xiāo).
示例
During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.
上面的replaces each with its first bound還有待研究. 個(gè)人理解應(yīng)該是說(shuō), <T extends First & Second & ...>這種情況, 泛型類(lèi)型被替換成First. 本來(lái)想反編譯class文件驗(yàn)證一下, 結(jié)果發(fā)現(xiàn)Java1.8編譯后的class文件, 反編譯后竟然看到了泛型參數(shù)...
關(guān)于使用idea自帶的反編譯工具反編譯class文件, 能看到泛型類(lèi)型的原因, 可以參考以下幾篇文章:
知乎這篇寫(xiě)得挺好, 推薦查看.
java 泛型擦除發(fā)生在哪個(gè)階段,如何用反編譯工具查看泛型擦除后的代碼?
java-generic-types-type-erasure
java-type-erasure-why-can-i-see-the-type-when-i-look-at-the-bytecode.
文章最后寫(xiě)了幾個(gè)反編譯的例子. 可到本文最后查看.
無(wú)界泛型參數(shù)
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
因?yàn)槭菬o(wú)界, 所以類(lèi)型參數(shù)直接被替換成Object了, 編譯后:
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
// ...
}
有界泛型參數(shù)
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
因?yàn)橛邢薅ㄟ吔? 所以直接被替換成Comparable了, 代碼如下:
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() { return data; }
// ...
}
泛型方法的類(lèi)型擦除
無(wú)界
// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
int cnt = 0;
for (T e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
編譯后:
public static int count(Object[] anArray, Object elem) {
int cnt = 0;
for (Object e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
有界
class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }
泛型方法:
public static <T extends Shape> void draw(T shape) { /* ... */ }
編譯后:
public static void draw(Shape shape) { /* ... */ }
類(lèi)型擦除的影響以及橋接方法
考慮下面兩個(gè)類(lèi)
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
繼承于泛型類(lèi):
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
調(diào)用情況:
MyNode mn = new MyNode(5);
Node n = mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = mn.data; // Causes a ClassCastException to be thrown.
類(lèi)型擦除后的代碼:
MyNode mn = new MyNode(5);
Node n = (MyNode)mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.
解析:
- n.setData("Hello"); causes the method setData(Object) to be executed on the object of class MyNode. (The MyNode class inherited setData(Object) from Node.)
- In the body of setData(Object), the data field of the object referenced by n is assigned to a String.
- The data field of that same object, referenced via mn, can be accessed and is expected to be an integer (since mn is a MyNode which is a Node<Integer>.
- Trying to assign a String to an Integer causes a ClassCastException from a cast inserted at the assignment by a Java compiler.
橋接方法
經(jīng)過(guò)類(lèi)型擦除之后, 上面兩個(gè)類(lèi)變成以下形式
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
可以看到兩個(gè)類(lèi)的方法setData其實(shí)不滿(mǎn)足Override的要求了, 因?yàn)閰?shù)類(lèi)型不同了.
為了解決這個(gè)問(wèn)題, 編譯器產(chǎn)生一個(gè)橋接方法, 用以維持多態(tài):
class MyNode extends Node {
// Bridge method generated by the compiler
//
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
// ...
}
使用javap反編譯來(lái)查看泛型類(lèi)型擦除的例子
對(duì)下面這段話的驗(yàn)證.
During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded.
兩個(gè)接口A和B:
package generictopic.typeerasure;
/**
* Created by xiaofu on 17-11-7.
*/
public interface A {
void a();
}
package generictopic.typeerasure;
/**
* Created by xiaofu on 17-11-7.
*/
public interface B {
void B();
}
類(lèi)FirstBoundA, 實(shí)現(xiàn)A和B接口, 且A寫(xiě)在前:
package generictopic.typeerasure;
/**
* Created by xiaofu on 17-11-7.
*/
public class FirstBoundA<T extends A & B> {
public void test(T item){
System.out.println(item);
}
}
類(lèi)FirstBoundB, 實(shí)現(xiàn)A和B接口, 且B寫(xiě)在前:
package generictopic.typeerasure;
/**
* Created by xiaofu on 17-11-7.
*/
public class FirstBoundB<T extends B & A> {
public void test(T item){
System.out.println(item);
}
}
編譯這幾個(gè)文件, 然后分別用javap進(jìn)行反編譯.
javap參數(shù)
這里為了簡(jiǎn)單, 直接使用-s參數(shù), 即Prints internal type signatures.
對(duì)FirstBoundA.class執(zhí)行反編譯操作:
javap -s FirstBoundA.class
可以看到對(duì)于test方法, descriptor部分顯示的是Lgenerictopic/typeerasure/A;, 即泛型類(lèi)型替換成第一個(gè)邊界A了.
Compiled from "FirstBoundA.java"
public class generictopic.typeerasure.FirstBoundA<T extends generictopic.typeerasure.A & generictopic.typeerasure.B> {
public generictopic.typeerasure.FirstBoundA();
descriptor: ()V
public void test(T);
descriptor: (Lgenerictopic/typeerasure/A;)V
}
再看對(duì)FirstBoundB.class的反編譯結(jié)果:
可以看出來(lái), 參數(shù)被替換為第一個(gè)邊界B了.
Compiled from "FirstBoundB.java"
public class generictopic.typeerasure.FirstBoundB<T extends generictopic.typeerasure.B & generictopic.typeerasure.A> {
public generictopic.typeerasure.FirstBoundB();
descriptor: ()V
public void test(T);
descriptor: (Lgenerictopic/typeerasure/B;)V
}
所以當(dāng)某個(gè)泛型參數(shù)有多個(gè)邊界的時(shí)候, 編譯器的做法是將類(lèi)型替換為第一個(gè)邊界. 不過(guò)要注意, 如果該類(lèi)型參數(shù)有多個(gè)邊界, 而且其中有一個(gè)是class的話, 一定要寫(xiě)在第一個(gè), 這是語(yǔ)法要求.