Thinking In Java Part10(泛型擦除)

1、泛型擦除  
    ArrayList<String> ArrayList<Integer> 我们可能认为类型不同,但其实它们是相同的
        ArrayList<Integer> arrays= new ArrayList<>();
        ArrayList<String> arrays1= new ArrayList<>();
        arrays.add(2);
        arrays1.add("s");
        // true
        System.out.println(arrays.getClass().equals(arrays1.getClass()));
        // true
        System.out.println(arrays.getClass() == (arrays1.getClass()));
    Class.getTypeParameters()将返回一个TypeVariable对象数据,表示有泛型声明所声明的类型参数。但是我们从输出中只能发现用作参数占位符的标识符。
        因此:在泛型代码内部,无法获得任何有关泛型参数类型的信息。
    public static void main(String[] args) {
        ArrayList<Integer> arrays= new ArrayList<>();
        HashMap<Integer, Double> maps= new HashMap<>();
        // E
        System.out.println(Arrays.toString(arrays.getClass().getTypeParameters()));
        // K V
        System.out.println(Arrays.toString(maps.getClass().getTypeParameters()));
    }
    我们可以知道诸如类型参数标识符和泛型类型边界这类信息——但是我们无法知道用来创建某个特定实例的实际的类型参数。
    Java泛型是使用擦除来实现的,意味着我们使用泛型时,任何具体的类型信息都被擦除,我们只知道我们在使用一个对象。

    泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界,比如List<T>将被擦除为List,普通类型变量没有指定边界会被擦除为Object

    使用擦除的正当理由:从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。 擦除使得先有的非泛型客户端代码能够在不改变的情况下继续使用,直至客户端准备好用泛型重写这些代码。因为它不会突然间破坏所有现有代码。

    擦除的代价是显著的,泛型不能用于显示地引用运行时类型的操作之中,例如转型、instanceof和new表达式。因为所有关于参数的信息都丢失了。我们在使用泛型时必须时刻提醒自己 我们只是看起来好像拥有有关参数的类型 信息而已。
3、擦除的补偿
    擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道确切类型的操作都无法完成
        public static void f(Object arg){
            // error
            if(arg instanceof  T){

            }
            // error
            T var  = new T();
            // error
            T[] array = new T[SIZE];
            //error
            T t = (T) new Object[SIZE];
        }
    因此有时必须通过引入类型标签来对擦除进行补偿。需要显式传递你的类型的Class对象,以便在类型表达式中使用它们。
    通过动态的isInstance来使用
    class Building{}
    class House extends Building{}
    class ClassTypeCapture<T>{
        Class<T> kind;

        public ClassTypeCapture(Class<T> kind) {
            this.kind = kind;
        }
        public boolean f(Object arg){
            return kind.isInstance(arg);
        }

        public static void main(String[] args) {
            ClassTypeCapture<Building> buildingClassTypeCapture = new ClassTypeCapture<>(Building.class);
            // true
            System.out.println(buildingClassTypeCapture.f(new Building()));
            // true
            System.out.println(buildingClassTypeCapture.f(new House()));
            ClassTypeCapture<House> House = new ClassTypeCapture<>(House.class);
            // false
            System.out.println(House.f(new Building()));
            // true
            System.out.println(House.f(new House()));
        }
    }
    编译器将确保类型标签可以匹配泛型参数
4、泛型类中创建一个new T()无法实现的部分原因是因为擦除,另一部分原因是编译器不能验证T具有默认(无参)构造器。Java的解决方案是传递一个工厂对象,并用来创建新的实例。
class ClassAsFactory<T>{
    T x;

    public ClassAsFactory(Class<T> kind) {
        try{
            x = kind.newInstance();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}
class Employee{}
public class InstantiateGenericType {
    public static void main(String[] args) {
        ClassAsFactory<Employee> employeeClassAsFactory = new ClassAsFactory<>(Employee.class);
        System.out.println("ccccc succed");
        try{
            ClassAsFactory<Integer> integerClassAsFactory = new ClassAsFactory<Integer>(Integer.class);
        } catch (Exception e){
            // java.lang.InstantiationException: java.lang.Integer
            System.out.println("failed:"+e);
        }
    }
}
    可以编译,但是会由于ClassAsFactory<Integer>失败,因为Integer没有任何默认的构造器。因为这个错误不是在编译器捕获的,因此SUn建议使用显式的工厂,并限制类型。
    interface Factory1<T>{
        T create();
    }
    class Foo2<T>{
        private T x;
        public <F extends Factory1<T>> Foo2(F factory){
            x = factory.create();
        }
    }
    class IntegerFactory implements  Factory1<Integer>{

        @Override
        public Integer create() {
            return new Integer(0);
        }
    }
    class Widget{
        public static class Factory implements Factory1<Widget>{
            @Override
            public Widget create(){
                return new Widget();
            }
        }
    }
    public class FactoryConstraint {
        public static void main(String[] args) {
            new Foo2<Integer>(new IntegerFactory());
            new Foo2<Widget>(new Widget.Factory());
        }
    }
    传递Class<T>的一种变体。两种方式都传递了工厂都系,Class<T>是内建的工厂对象,上面显式创建了一个工厂对象,可以获得编译时检查。
    另一种方式为模版方法设计模式。get()是模版方法,create是在子类中定义的、用来产生子类类型的对象。
    abstract class GenericWithCreate<T>{
        final T element;

        public GenericWithCreate() {
            element = create();
        }
        abstract T create();
    }
    class X{}
    class Creator extends GenericWithCreate<X>{
        @Override
        X create(){return new X();}
        void f(){
            System.out.println(element.getClass().getSimpleName());
        }
    }
    public class CreatorGeneric {
        public static void main(String[] args) {
            Creator creator = new Creator();
            creator.f();
        }
    }
5、泛型数组
    一般来说不能创建泛型数组,解决方案为创建泛型数组的地方使用ArrayList
    public class ListOfGeneric {
        private List<T> array = new ArrayList<T>();
        public void add(T item){array.add(item);}
        public T get(int index){return array.get(index);}
    }
    如果仍旧希望创建泛型数组的话,可以按照编译器喜欢的方式定义一个引用
    class Generic<T> {
    }

    public class ArrayOfGenericReference {
        static Generic<Integer>[] gia;
        static final int SIZE = 100;

        public static void main(String[] args) {
            // java.lang.ClassCastException
    //       gia = (Generic<Integer>[]) new Object[SIZE];
            gia = (Generic<Integer>[]) new Generic[SIZE];
            // Generic[]
            System.out.println(gia.getClass().getSimpleName());
            gia[0] = new Generic<Integer>();
            // 编译时异常
    //        gia[1] = new Object();
    //        // 编译时异常
    //        gia[2] = new Generic<Double>();
        }
    }
    由于数组跟踪它们的实际类型,这个类型是数组被创建时确定的,因此gia这个泛型数组已经被转型为Generic<Integer>[],但是这个信息只存在编译器,在运行时,它仍旧是Object数组,会有问题。成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后进行转型。
    建立一个简单的泛型数组包装器。
    public class GenericArray<T> {
        private T[] array;

        public GenericArray(int sz) {
            this.array = (T[]) new Object[sz];
        }
        public void put(int index,T item){
            array[index] = item;
        }
        public T get(int index){return array[index];}
        public T[] rep(){return array;}

        public static void main(String[] args) {
            GenericArray<Integer> integerGenericArray = new GenericArray<>(10);
            //java.lang.ClassCastException
    //        Integer[] ia = integerGenericArray.rep();
            Object[] rep = integerGenericArray.rep();
        }
    }
    因为我们不能声明T[] array = new T[sz],隐藏我们创建了一个对象数组,然后转型。
    rep方法返回T[],在main中用于integerGenericArray,应该返回Integer[],但是如果调用,并尝试用Integer[]来捕获结果会得到ClassCastException,这是因为实际的运行时类型是Object[].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值