本文首发于个人网站:Java阿杜
类型擦除
学过C++模板的,在使用Java泛型的时候,会感觉到有点不疑问,例如:(1)无法定义一个泛型数组、无法调用泛型参数对象中对应的方法(当然,通过extends关键字是可以做到,只是比较麻烦);(2)ArrayList和ArrayList在运行时的类型是相同的。Java中的泛型有这些问题,是它的实现机制决定的,即“类型擦除”。
类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类; ```java public class Holder4
{ private T a; private T b; private T c;
public Holder4(T a, T b, T c) {
this.a = a;this.b = b;this.c = c;
}
public T getA() {
return a;
}
public T getB() {
return b;
}
public T getC() {
return c;
}
public void setA(T a) {
this.a = a;
}
public void setB(T b) {
this.b = b;
}
public void setC(T c) {
this.c = c;
}
public static void main(String[] args) {Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型Automobile b = holder4.getB();Automobile c = holder4.getC();}
}
<br />在Java中,每定义一个泛型类型,就会自动提供一个对应的原始类型,例如:```javapublic class Holder4Raw {private Object a;private Object b;private Object c;public Holder4Raw(Object a, Object b, Object c) {this.a = a;this.b = b;this.c = c;}public Object getA() {return a;}public Object getB() {return b;}public Object getC() {return c;}public void setA(Object a) {this.a = a;}public void setB(Object b) {this.b = b;}public void setC(Object c) {this.c = c;}public static void main(String[] args) {Holder4Raw holder4Raw = new Holder4Raw(new Automobile(),new Automobile(), new Automobile());Automobile a = (Automobile) holder4Raw.getA(); //显示的转型Automobile b = (Automobile) holder4Raw.getB();Automobile c = (Automobile) holder4Raw.getC();}}
为什么选择这种实现机制?
- 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型;
- Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了“类型擦除”这种折中的实现方式。
Java泛型依赖编译器实现,只存在于编译期,JVM中没有泛型的概念;那么,编译器做了什么工作呢?(1)set方法是编译期检查;(2)get方法的返回值进行转型,编译器插入了一个checkcast语句。
我们通过字节码进行观察,可以看出:(1)Holder4和Holder4Raw两个类的字节码完全相同;(2)在main函数的33、41和49行就是编译器插入的checkcast语句;public class org.java.learn.generics.Holder4<T> {public org.java.learn.generics.Holder4(T, T, T);Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: aload_16: putfield #2 // Field a:Ljava/lang/Object;9: aload_010: aload_211: putfield #3 // Field b:Ljava/lang/Object;14: aload_015: aload_316: putfield #4 // Field c:Ljava/lang/Object;19: returnpublic T getA();Code:0: aload_01: getfield #2 // Field a:Ljava/lang/Object;4: areturnpublic T getB();Code:0: aload_01: getfield #3 // Field b:Ljava/lang/Object;4: areturnpublic T getC();Code:0: aload_01: getfield #4 // Field c:Ljava/lang/Object;4: areturnpublic void setA(T);Code:0: aload_01: aload_12: putfield #2 // Field a:Ljava/lang/Object;5: returnpublic void setB(T);Code:0: aload_01: aload_12: putfield #3 // Field b:Ljava/lang/Object;5: returnpublic void setC(T);Code:0: aload_01: aload_12: putfield #4 // Field c:Ljava/lang/Object;5: returnpublic static void main(java.lang.String[]);Code:0: new #5 // class org/java/learn/generics/Holder43: dup4: new #6 // class org/java/learn/generics/Automobile7: dup8: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V11: new #6 // class org/java/learn/generics/Automobile14: dup15: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V18: new #6 // class org/java/learn/generics/Automobile21: dup22: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V25: invokespecial #8 // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V28: astore_129: aload_130: invokevirtual #9 // Method getA:()Ljava/lang/Object;33: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型36: astore_237: aload_138: invokevirtual #10 // Method getB:()Ljava/lang/Object;41: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型44: astore_345: aload_146: invokevirtual #11 // Method getC:()Ljava/lang/Object;49: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型52: astore 454: return}
参考资料
- 《Java编程思想》
- 《Effective Java》
- 《Java核心技术》
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

