Java泛型的实现原理是类型擦除,想要用好泛型,解决一些泛型的“疑难杂症”问题,就要正确理解和使用类型擦除。
泛型学习资料:《泛型最全知识导图》、《大厂泛型面试真题26道》,到本篇结尾处获得~
泛型是 Java 1.5 版本引进的新特性,Java 1.5 前没有泛型。但是,为什么泛型的代码和之前版本的代码能够很好地兼容呢?
这是因为,泛型信息只存在于代码编译时,在进入 JVM 之前,与泛型相关的信息就会被擦掉,专业术语叫做类型擦除。也可以简单理解为:将泛型 Java 代码,转换为普通 Java 代码,只是编译器更直接,将泛型 Java 代码,直接转换成了普通 Java 字节码。
类型擦除的关键,是从泛型类型中清除类型参数的相关信息,在必要时,再添加类型检查和类型转换的方法。
在泛型中使用类型擦除,主要是为了“向后兼容”,保证 1.5 版本的程序,在 8.0 版本上也可以运行,让非泛型的 Java 程序,在后续支持泛型的 JVM 上也可以运行。
代码示例:
下面展示的两种代码,在编译成 Java虚拟机汇编码是一样的。因此,无论函数的返回类型是T,还是我们主动写强转,最后都是插入一条 checkcast 语句而已。
class SimpleHolder{
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
SimpleHolder holder = new SimpleHolder();
holder.setObj("Item");
String s = (String)holder.getObj();
class GenericHolder{
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
GenericHolder holder = new GenericHolder();
holder.setObj("Item");
String s = holder.getObj();
aload_1
invokevirtual // Method get: ()Object
checkcast // class java/lang/String
astore_2
return
我们可以理解为:
这样既把非泛型“扩展为泛型”,又兼容了非泛型。
Java 的泛型使用类型擦除,只是在编译时做类型检查、在运行时擦除,共享代码好,但是类型精度一般。
类型擦除的过程:
代码示例:
interface Comparable {
public int compareTo( A that);
}
final class NumericValue implements Comparable {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
}
-----------------
class Collections {
public static >A max(Collection xs) {
Iterator xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList ();
numberList .add(new NumericValue((byte)0));
numberList .add(new NumericValue((byte)1));
NumericValue y = Collections.max( numberList );
}
}
经过类型擦除后的类型:
interface Comparable {
public int compareTo( Object that);
}
final class NumericValue implements Comparable {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
public int compareTo(Object that) { return this.compareTo((NumericValue)that); }
}
-------------
class Collections {
public static Comparable max(Collection xs) {
Iterator xi = xs.iterator();
Comparable w = (Comparable) xi.next();
while (xi.hasNext()) {
Comparable x = (Comparable) xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList();
numberList .add(new NumericValue((byte)0)); ,
numberList .add(new NumericValue((byte)1));
NumericValue y = (NumericValue) Collections.max( numberList );
}
}
第一段代码示例中,泛型类 Comparable 擦除后, A 被替换为最左边界 Object 。Comparable
第二段代码示例中,限定了类型参数的边界 >A , A 必须为Comparable 的子类。按照类型擦除的过程,先将所有的类型参数替换为最左边界Comparable,然后去掉参数类型 A ,得到最终擦除后的结果。
这是一道经典的测试题:
List l1 = new ArrayList();
List l2 = new ArrayList();
System.out.println(l1.getClass() == l2.getClass());
输出结果是 true ,是因为 List
可能有同学会问,类型 String 和 Integer 怎么办?
答案是泛型转译。
public class Erasure {
T object;
public Erasure(T object) {
this.object = object;
}
}
输出结果:
erasure class is:com.frank.test.Erasure
Class 的类型仍然是 Erasure 形式,而不是 Erasure
那么,泛型类中 T 的类型,在 JVM 中是什么类型呢?
Field[] fs = eclz.getDeclaredFields();
for ( Field f:fs) {
System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}
输出结果:
Field name object type:java.lang.Object
是不是说,泛型类被类型擦除后,相应的类型就被替换成了 Object 类型呢?
这种说法,不完全正确。
我们更改一下代码。
public class Erasure {
// public class Erasure {
T object;
public Erasure(T object) {
this.object = object;
}
}
输出结果:
Field name object type:java.lang.String
我们现在可以下结论了,在泛型类被类型擦除时,之前泛型类中的类型参数:
所以,在反射中:
public class Erasure {
T object;
public Erasure(T object) {
this.object = object;
}
public void add(T object){
}
}
add() 这个方法对应的 Method 的签名,应该是 Object.class。
Erasure erasure = new Erasure("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());
Method[] methods = eclz.getDeclaredMethods();
for ( Method m:methods ){
System.out.println(" method:"+m.toString());
}
输出结果:
method:public void com.frank.test.Erasure.add(java.lang.Object)
如果要在反射中找到 add 对应的 Method,我们应该调用 getDeclaredMethod("add",Object.class),否则程序会报错,提示没有这么一个方法,原因就是类型擦除时,T 被替换成 Object 类型了。
Java 泛型的实现原理是类型擦除,理解类型擦除,有利于我们绕过开发当中可能遇到的雷区,也能让我们绕过泛型本身的一些限制。
但是,类型擦除自身也有一些局限性,它会擦除掉很多继承相关的特性,引发了一些新的问题,具体我们在下一篇连载中详解。
以上,是关于类型擦除 type erasure 的介绍。实践出真知,利于消化,建议大家多动手练习。
我是大全哥,持续更新成体系的 Java 核心技术。
知识成体系,学习才高效,如果觉得有帮助,请顺手 点赞 支持下,谢谢。
我们下期见~
附泛型学习资料:
1 《泛型知识全景导图》
快速构建泛型知识体系,高清版本原图,几乎囊括了所有泛型核心知识点。
泛型知识全景导图
2 《大厂泛型面试真题26道》
精选大厂高频泛型面试题,都是我最新整理的,备面、复习时都可以查看。
大厂泛型面试题26道
--- end ---
留言与评论(共有 0 条评论) “” |