• Java擦除


    概述:

       Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变),奇怪的ClassCastException等。 正确的使用Java泛型需要深入的了解Java的一些概念,如协变,桥接方法,以及这篇笔记记录的类型擦除。Java泛型的处理几乎都在编译器中进行,编译器生成的bytecode是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。

    编译器如何处理泛型:

    通常情况下,一个编译器处理泛型有两种方式:
         1.Code specialization
    。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对stringintegerfloat产生三份目标代码。
         2.Code sharing
    。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
         C++
    中的模板(template)是典型的Code specialization实现。C++编译器会为每一个泛型类实例生成一份执行代码。执行代码中integer liststring list是两种不同的类型。这样会导致代码膨胀(code bloat),不过有经验的C++程序员可以有技巧的避免代码膨胀。
         Code specialization
    另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。
         Java
    编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。

       类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。
         
    类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。
         
    类型擦除的主要过程如下:
         1.
    将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
         2.
    移除所有的类型参数。

    擦除使我们在泛型代码内部,无法获得任何有关参数类型的信息。很蛋疼...

    例如:

    C++中我们可以这样写:

    template<typename T> T imax(T a, T b) {
        T copy;  
        return copy;
    }
    class A{
    
    };
    

      

    但是在Java中我们是不能够生成copy的因为们压根就不知道T的类型信息。

    那为什么Java要使用擦除呢?

    首先能够节省空间避免代码膨胀,主要原因是为了“迁移兼容性”,即允许泛型代码与非泛型代码共存,因为泛型是Java后期才添加的为了兼容以前的代码所以采取了折中的办法。

    那么擦除所带来的问题我们如何解决呢?

    1: 通过引入类型标签来对擦除进行补偿:

    class Building{
    	@Override
    	public String toString() {
    		return "Building ...";
    	}
    }
    class House extends Building{
    	@Override
    	public String toString() {
    		return "House ...";
    	}
    }
    
    class TestItem<T>{
    	Class<T> type;  //通过添加类型标签,来获得我要持有的类型的信息
    	public TestItem(Class<T> type) {
    		this.type = type;
    	}
    	public T getInstance() throws InstantiationException, IllegalAccessException {
    		T copy = type.newInstance(); //这样我就可以利用类型信息进行必要的处理了
    		return copy;
    	}
    }
    
    public class Test {
    	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    		TestItem<Building> item = new TestItem<>(Building.class);
    		System.out.println(item.getInstance());
    		System.out.println("------------------------");
    		TestItem<House> item2 = new TestItem<>(House.class);
    		System.out.println(item2.getInstance());
    		
    		//出错,因为我们是利用newInstance来创建对象的,就必须保证我们的对象要有默认的构造方法才行,但是Integer没有
    //		System.out.println("------------------------");
    //		TestItem<Integer> item3 = new TestItem<>(Integer.class);
    //		System.out.println(item3.getInstance());
    	}
    }

    上面的Integer的问题,我们可以通过传入一个工厂来实现

    interface Factory<T>{
    	T create();
    }
    class IntegerFactory implements Factory<Integer> {
    	@Override
    	public Integer create() {
    		return new Integer(0);
    	}
    }
    
    
    class TestItem<T>{
    	Class<T> type;  //通过添加类型标签,来获得我要持有的类型的信息
    	T copy;
    	Factory<T> factory;
    	public <F extends Factory<T>>TestItem(F factory) {
    		this.factory = factory;
    	}
    	public TestItem(Class<T> type) {
    		this.type = type;
    	}
    	public T getInstance() throws InstantiationException, IllegalAccessException {
    		//copy = type.newInstance(); //这样我就可以利用类型信息进行必要的处理了
    		copy = factory.create();
    		return copy;
    	}
    }
    
    public class Test {
    	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    //		TestItem<Building> item = new TestItem<>(Building.class);
    //		System.out.println(item.getInstance());
    //		System.out.println("------------------------");
    //		TestItem<House> item2 = new TestItem<>(House.class);
    //		System.out.println(item2.getInstance());
    		
    		//现在把工厂放进去就可以了。
    		System.out.println("------------------------");
    		TestItem<Integer> item3 = new TestItem<>(new IntegerFactory());  
    		System.out.println(item3.getInstance());
    	}
    }
    

      

    2: 同样我们可以通过使用设置擦相互边界来补偿擦除

    就像我们在前一篇的比较的时候我们将擦除边界设定成了Comparable,保证了我们的类新信息是可比较的。

    http://www.cnblogs.com/E-star/p/3438226.html

    注意:

    1.虚拟机中没有泛型,只有普通类和普通方法
    2.
    所有泛型类的类型参数在编译时都会被擦除
    3.
    创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
    4.
    不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。

  • 相关阅读:
    zcu106 sd卡mount错误
    petalinux如何保留u-boot和kernel源码
    mali开发板
    v550 bare-metal 裸机 结构
    make打印隐含变量和隐含规则
    麒麟加速器
    petalinux定制rootfs 加入iperf memtester ethtool
    kafka分区选主机制
    java8 String intern()
    Storm之配置文件
  • 原文地址:https://www.cnblogs.com/E-star/p/3438290.html
Copyright © 2020-2023  润新知