27.Android架构-泛型擦除机制

2020-08-30  本文已影响0人  任振铭
什么是泛型擦除

Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种
伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,
所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。

定义一个泛型接口

interface Plate<R>{
    void set(R r);
    R get();
}

使用ASM ByteCode Viewer查看他的字节码


批注 2020-08-30 101425.png

可以看到我们设置的泛型R,被擦除为Object了,这就是泛型擦除

// class version 52.0 (52)
// access flags 0x600
// signature <R:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: Plate<R>
abstract interface Plate {

  // compiled from: TestFanxing.java

  // access flags 0x401
  // signature (TR;)V
  // declaration: void set(R)
  public abstract set(Ljava/lang/Object;)V

  // access flags 0x401
  // signature ()TR;
  // declaration: R get()
  public abstract get()Ljava/lang/Object;
}

定义一个Plate1实现Plate接口

class Plate1<R> implements Plate<R>{
    private R r;
    @Override
    public void set(R r) {
        this.r = r;
    }
    @Override
    public R get() {
        return r;
    }
}

他的bytecode为

class Plate1 implements Plate {
  private Ljava/lang/Object; r
  <init>()V
    ......  

  public set(Ljava/lang/Object;)V
    ...... 

  public get()Ljava/lang/Object;
    ......  

}

定义一个指定泛型类型的Plate2实现Plate接口

class Plate2<R extends Comparable<R>> implements Plate<R>{
    private R r;
    @Override
    public void set(R r) {
        this.r = r;
    }
    @Override
    public R get() {
        return r;
    }
}

可以看到我们限定了泛型的类型,那么他的bytecode是什么样的?


class Plate2 implements Plate {

  private Ljava/lang/Comparable; r

  <init>()V
       ......
  public set(Ljava/lang/Comparable;)V
       ......
  public get()Ljava/lang/Comparable;
       ......
  public synthetic bridge get()Ljava/lang/Object;
       ......
  public synthetic bridge set(Ljava/lang/Object;)V
   L0
    LINENUMBER 43 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST java/lang/Comparable
    INVOKEVIRTUAL Plate2.set (Ljava/lang/Comparable;)V
    RETURN
   L1
    LOCALVARIABLE this LPlate2; L0 L1 0
    // signature LPlate2<TR;>;
    // declaration: this extends Plate2<R>
    MAXSTACK = 2
    MAXLOCALS = 2
}

可以看到虽然我们在Plate2中只定义了一个set get方法,但是bytecode中却有两个,其中一个get set方法添加了synthetic bridge 表示这是一个桥接方法,作用是为了保持多态性,可以看到CHECKCAST java/lang/Comparable,检查类型是否为Comparable,如果是的话再去调用上边的public set(Ljava/lang/Comparable;)V方法。可以这样理解,set(Ljava/lang/Object;)V是从Plate接口实现来的,set(Ljava/lang/Comparable;)V是他本身的,因为限定了类型范围

Java编译器具体是如何擦除泛型的

  1. 检查泛型类型,获取目标类型
  2. 擦除类型变量,并替换为限定类型
    如果泛型类型的类型变量没有限定(<T>),则用Object作为原始类型
    如果有限定(<T extends XClass>),则用XClass作为原始类型
    如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
  3. 在必要时插入类型转换以保持类型安全
  4. 生成桥方法以在扩展时保持多态性

泛型擦除的残留

上边我们是通过showbytecode的方式查看的字节码,但是如果你点开类生成的class文件,你会发现,泛型既然被擦除了为什么在class中仍然可以看到?其实这里看到的只是签名而已,还保留了定义的格式,这样对分析字节码有好处。你甚至可以通过javap -c Plate2.class反编译class,你会发现,R还是能被看到,我们要看bytecode,通过showbytecode的方式比较真实


图片.png

泛型既然被擦除了,为什么通过反射还可以拿到泛型的类型?其实在类的常量池中保存了泛型信息



import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

public class TestFanxing {
    Map<String, String> map;
    //擦除 其实在类常量池里面保留了泛型信息
    public static void main(String[] args) throws Exception {
        Field f = TestFanxing.class.getDeclaredField("map");
        /**
         *  getType() 和 getGenericType()的区别 :
         * 1.首先是返回的类型不一样,一个是Class对象一个是Type接口。
         * 2.如果属性是一个泛型,从getType()只能得到这个属性的接口类型。但从getGenericType()还能得到这个泛型的参数类型。
         * 3.getGenericType()如果当前属性有签名属性类型就返回,否则就返回 Field.getType()。
         */
        System.out.println("f.getGenericType() "+f.getGenericType());                               // java.util.Map<java.lang.String, java.lang.String>
        System.out.println("f.getType() "+f.getType());                               // java.util.Map<java.lang.String, java.lang.String>
        //ParameterizedType是Type的子接口,表示一个有参数的类型,也就是包含泛型
        System.out.println(f.getGenericType() instanceof ParameterizedType);  // true
        ParameterizedType pType = (ParameterizedType) f.getGenericType();
        //getRawType(): 返回承载该泛型信息的对象, 如上面那个Map<String, String>承载范型信息的对象是Map
        System.out.println("pType.getRawType() "+pType.getRawType());                               // interface java.util.Map
        for (Type type : pType.getActualTypeArguments()) {
            // getActualTypeArguments(): 返回实际泛型类型列表, 如上面那个Map<String, String>实际范型列表中有两个元素, 都是String
            System.out.println("pType.getActualTypeArguments() "+type);                                         // 打印两遍: class java.lang.String
        }
        //Type getOwnerType(): 返回是谁的member.(上面那两个最常用)
        System.out.println("pType.getOwnerType() "+pType.getOwnerType());                             // null
    }
}

使用泛型以及泛型擦除带来的副作用

1. 泛型类型变量不能使用基本数据类型

比如没有ArrayList<int>,只有ArrayList<Integer>.当类型擦除后,ArrayList的原始类中的类型变量(T)替换成Object,但Object类型不能 存放int值


图片.png
2. 不能使用instanceof 运算符

因为擦除后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,所有没法使用instanceof


图片.png
3. 泛型在静态方法和静态类中的问题

因为泛型类中的泛型参数的实例化是在定义泛型类型对象 (比如ArrayList<Integer>)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么


图片.png
4. 泛型类型中的方法冲突

因为擦除后两个equals方法变成一样的了


图片.png
5. 没法创建泛型实例

因为类型不确定

图片.png
6. 没有泛型数组

因为数组是协变(在某些情况下,即使某个对象不是数组的基类型,我们也可以把它赋值给数组元素。这种属性叫做协变(covariance)),擦除后就没法满足数组协变的原则

图片.png
上一篇下一篇

猜你喜欢

热点阅读