您的位置 首页 java

Java泛型:类型擦除 type erasure

Java泛型:类型擦除 type erasure

Java 泛型的实现原理是类型擦除,想要用好泛型,解决一些泛型的“疑难杂症”问题,就要正确理解和使用类型擦除。

泛型学习资料:《泛型最全知识导图》、《大厂泛型面试真题26道》,到本篇结尾处获得~

1 什么是类型擦除(Type Erasure)

泛型是 java 1.5 版本引进的新特性,Java 1.5 前没有泛型。但是,为什么泛型的代码和之前版本的代码能够很好地兼容呢?

这是因为, 泛型信息只存在于代码编译时,在进入 JVM 之前,与泛型相关的信息就会被擦掉,专业术语叫做类型擦除 。也可以简单理解为:将泛型 Java 代码,转换为普通 Java 代码,只是编译器更直接,将泛型 Java 代码,直接转换成了普通 Java 字节码

类型擦除的关键,是从泛型类型中清除类型参数的相关信息,在必要时,再添加类型检查和类型转换的方法。

2 为什么要用类型擦除

在泛型中使用类型擦除,主要是为了“向后兼容”,保证 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<T>{
    private T obj;
    public T getObj() {
        return obj;
    }
    public void setObj(T obj) {
        this.obj = obj;
    }
}
GenericHolder<String> holder = new GenericHolder<String>();
holder.setObj("Item");
String s = holder.getObj();  
 aload_1
invokevirtual // Method get: ()Object
checkcast // class java/lang/String
astore_2
return  

我们可以理解为:

  • 之前非泛型的写法,编译成的虚拟机汇编码块是 A ;
  • 之后的泛型写法,只是在 A 的前面、后面“插入”了其它的汇编码,这并不会破坏 A 这个整体。

这样既把非泛型“扩展为泛型”,又兼容了非泛型。

3 类型擦除的优缺点

Java 的泛型使用类型擦除,只是在编译时做类型检查、在运行时擦除,共享代码好,但是类型精度一般。

4 类型擦除的使用原则

  • 消除类型参数声明,即删除 <> 、及其包围的部分;
  • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符、或没有上下界限定,则替换为Object;如果存在上下界限定,则根据子类替换原则,取类型参数的最左边限定类型(即父类);
  • 为了保证类型安全,必要时插入强制类型转换代码;
  • 自动产生“桥接方法”,以保证擦除类型后的代码仍然具有泛型的“多态性”。

5 类型擦除的过程

类型擦除的过程:

  • 将所有的泛型参数,用其最左边界类型(最顶级的父类型)替换;
  • 移除所有的类型参数。

代码示例:

 interface Comparable <A> {
  public int compareTo( A that);
}
final class NumericValue implements Comparable <NumericValue> {
  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  extends  Comparable<A>>A max(Collection <A> xs) {
     Iterator  <A> 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 <NumericValue>  number List = new LinkedList <NumericValue> ();
    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> 擦除后, A 被替换为最左边界 Object 。Comparable<NumericValue> 的类型参数 NumericValue 被擦除掉,这直接导致了 NumericValue 没有实现接口 Comparable 的 compareTo(Object that) 方法。于是,编译器充当好人,添加了一个桥接方法。

第二段代码示例中,限定了类型参数的边界 <A extends Comparable<A>>A , A 必须为Comparable<A> 的子类。按照类型擦除的过程,先将所有的类型参数替换为最左边界Comparable<A>,然后去掉参数类型 A ,得到最终擦除后的结果。

6 类型擦除的使用示例

这是一道经典的测试题:

 List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
        
System.out.println(l1.getClass() == l2.getClass());  

输出结果是 true ,是因为 List<String>和 List<Integer>,在 JVM 中的 Class 都是 List.class ,泛型信息被擦除了。

可能有同学会问,类型 String 和 Integer 怎么办?

答案是 泛型转译

 public class Erasure <T>{
    T object;
    public Erasure(T object) {
        this.object = object;
    }
}  

输出结果:

 erasure class is:com.frank.test.Erasure  

Class 的类型仍然是 Erasure 形式,而不是 Erasure<T>这种形式。

那么,泛型类中 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 <T extends String>{
//	public class Erasure <T>{
    T object;

    public Erasure(T object) {
        this.object = object;
    }
}
  

输出结果:

 Field name object type:java.lang.String  

我们现在可以下结论了,在泛型类被类型擦除时,之前泛型类中的类型参数:

  • 如果没有指定上限,如 <T> ,类型参数就会被转译成普通的 Object 类型;
  • 如果指定了上限,如 <T extends String>,类型参数就被替换成类型上限。

所以,在反射中:

 public class Erasure <T>{
    T object;
    public Erasure(T object) {
        this.object = object;
    }
    
    public void add(T object){
    }
}  

add() 这个方法对应的 Method 的签名,应该是 Object.class。

 Erasure<String> erasure = new Erasure<String>("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

文章来源:智云一二三科技

文章标题:Java泛型:类型擦除 type erasure

文章地址:https://www.zhihuclub.com/198388.shtml

关于作者: 智云科技

热门文章

网站地图