美文网首页
27.Android架构-泛型擦除机制

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

作者: 任振铭 | 来源:发表于2020-08-30 19:38 被阅读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

    相关文章

      网友评论

          本文标题:27.Android架构-泛型擦除机制

          本文链接:https://www.haomeiwen.com/subject/vpnssktx.html