技术开发 频道

JAVA泛型浅析


IT168技术文档】 
    继承关系: 
    1、 原始继承:就是我们经常提到的继承关系,如ArrayList是List的子类; 
    2、 泛化继承: 
        a) 泛化后的ArrayList<T>依旧是List<T>的子类;其中T是参数化类型 
        b) 如果类型T是类型B的子类,那么List<T>不是List<B>的子类 
        c) List<T>是List<? extends T>的子类 
        d) List<T>是List<? extends B>的子类 
        e) List<T>是List<? super T>的子类 
        f) List<B>是List<? super T>的子类 
        g) 如果类型T是类型B的子类,那么T[]是B[]的子类 
    3、 关于协变式(covariant)、不变式(invariant)和反变式(contravariant): 
        a) 数组和扩展类(extends)的泛化是协变式,即如果类型T是类型B的子类,那么T[]是B[]的子类;List<T>是List<? extends B>的子类 
        b) 非扩展类泛型是不变式,即如果类型T是类型B的子类,那么List<T>不是List<B>的子类 
        c) Super类泛型是反变式,即如果B是T的超类,则List<B> 是List<? super T>的子类 

    Get和Put原则: 
    当从一个泛化的结构中取数据的时候请使用extends通配,当往一个泛化的结构中放数据的时候请使用super通配; 
    当需要同时从一个泛化结构中读取和写入数据是,请不使用通配符号; 
    为什么会这样,我们简单的分析一下:假定类型T继承自A和B,类型C和D又从T类型继承,那么List<? extends T>中存放的只能是T类型或是C或是D类型,里面存放的类型都可以向上cast到T,所以从List<? extends T>中取东西编译器能够正确处理,只要映射到T就可以了(T是他们的父类)!往List<? extends T>放东西就不一样的,原来里面放的是T/C还是D都被擦除成T,所以编译器不知道原来到底存放的是什么类型,无法保证类型安全,所以这个操作被禁止! 
    List<? super T>中存放的是A/B或是T,往List<? super T>放T是允许的,因为T总是可以向上转换成A或是B,但是从里面取东西就有问题了,编译器还是不能够确定List里面放的是什么类型,可能是A也可能是B。 

    具体化: 
    当一个类型能够在运行时态被完整的表现,我们就称为其是可以具体化的,举一个例子: 
    List<Integer>就不是一个可以具体化的类型,因为它在运行时态被擦除成List!所以当处理一些需要运行时检测类型的操作的时候(如instanceof)就要特别注意。 
    究竟哪些类型是可具体化的呢; 
        (1)、原始类型,如int; 
        (2)、非参数化的类和接口; 
        (3)、非限定的参数化类型,如List<?>, Map<?,?> 
        (4)、Raw类型,如 List,ArrayList等 
        (5)、所有由可具体化类型组成的数组,如Number[],List<?>[]等 
    哪些是不可具体化的类型呢 
        (1)、类型变量,如T; 
        (2),参数化类型,如List<Number>等 
        (3),有限定的参数化类型,如List<? extends Number>; 
    具体哪些操作需要注意区分是否是具体化类型: 
        (1)、实例检测和投射的时候; 
        Java中判断一个对象是否是一个类型的实例的时候,都使用instanceof整个运算符号,这个运算符号是需要利用运行时信息的,这样就要求其右边的类型必须是可以具体化的类型,如:o instanceof Integer 但是不能是 o instanceof List<T>,因为List<T>不是具体化类型,T已经在编译的时候被擦除了,所以没有足够的信息判断前面的表达式是否成功。 
        同样的道理也适用于类型转换,(Integer)o是可以的,但是(List<T>)o就会报为检测错! 
        (2)、catch子句: 
        在捕获异常的时候,catch子句也是运用运行时信息来判定一个异常是不是其所关心的,这就要求写在catch子句后面的类型必须是可具体化的类型,同时有必须是Throwable的子类!同时Java也不允许创建一个参数化的Throwable子类。
Try{… }catch(FooException<T> e){…} //编译错误 Public class FooExceptioin<T> extends Exception //编译错误
        (3)、创建数组: 
        任何参与创建数组的类型必须是可具体化类型!所以下面的语句肯定是错误的: 
        T[] a = new T[size]; 其中T是参数类型。原因很简单,数组是需要运用运行时信息来检测类型的完整性,如: 
        Integer[] ints = new … 
        Number[] nums = ints //Integer[] 是Number[]的子类 
        Nums[0] = 8.9 //编译不会报错,但运行时异常,正说明这个问题 
        注意:这里说的是不可以创建,而不是不能声明,你还是可以声明,如在函数的参数中,如: 
         public static<T> void foo(T[] a){…} 
        (4)、变长参数: 
        变长参数是JDK1.5新引入的功能,它允许一个方法接受不确定数目的参数,并最终将这些参数放入一个数组。这里既然是数组,同样需要满足上面3)所提到的约束,如:
public static<E> void addAll(List<E> list, E... arr){ for(E itor : arr){ list.add(itor); } } public static<E> List<E> foo(E a){ List<E> list = new ArrayList<E>(); addAll(list,a); // 编译器会给出警告 return list; }
0
相关文章