JAVA泛型与类型安全

作者: 白与兰与白兰地 | 来源:发表于2019-02-25 16:01 被阅读4次

    1. 基础泛型

    //定义泛型类,接口的定义和类一样
    class A<T,P extends Number> {
      T t;
      P p;// extends 限定泛型的范围,等于或者是extends的子类;通配符才有extends和super
      public <M> M doSomething(M params) {}
    }
    

    2. 协变与逆变与不变

    • 协变

    简单来说即:

    A extends B  ==>  A[] extends B[]
    B[] bs = new A[2]; 
    

    Java中的数组是协变的

    • 逆变
      与协变相对,逆转了类型关系

    • 不变

    Java 语言中的泛型基本上完全在编译器中实现,由编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码。这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,在生成字节码之前将其清除,替换为非泛型上界)

    由于泛型擦除的原因,Java中的泛型是不变的,因此在使用泛型的地方(比如集合)中

    List<B> bls = new ArrayList<A>();//不合法
    

    编译器检查不通过,这是因为B可以有其它的子类比如C,由于bls的引用指定的类型是B,所以

    C extends B
    bls.add(new C())
    

    是合法的,假设bls指向的实例指定的类型是A成立,而A与C并没有直接联系,则编译器认为上述添加会导致类型不安全。

    为了解决泛型的不变,就需要用到第三部分的通配符?与extends、super。

    3. 通配符? 与 extends 与 super

    有以下合法的写法

    //通配符?在单独使用时可以看作是? extends Object 
    List<?> l0= new ArrayList<Object>();
    
    //extends包含指向类及其子类
    List<? extends Number> l1= new ArrayList<Number>();
    List<? extends Number> l2= new ArrayList<Integer>();
    
    //super包含指向类及其父类
    List<? super Integer> l3 = new ArrayList<Integer>();
    List<? super Integer> l4 = new ArrayList<Number>();
    

    尽管如上所写都能够正确的通过编译器的检查,但是在实际使用时却并不是这么顺利,比如

    ? extends xxx使得泛型具有协变的特性
    l1.add(new Integer(1));//无法编译
    Number n = l1.get(0);//可以获取到
    

    当向l1中add一个元素,不管是任何类型的元素,都是不合法的,因为泛型下界无法确定,比如Integer和Double同是Number的子类,但是一个指向List<Integer>的实例add一个Double类型的元素显然是不合法的;当从l1中get一个元素,泛型上界确定是Number

    ? super xxx使得泛型具有逆变的特性
    l3.add(new Integer(1));//可以编译
    Object o = l3.get(0);//可以获取到
    

    想要向l3中add一个元素,泛型下界确认为Integer,那么这个元素必须是Integer及其子类的类型;当从l3中get一个元素时,泛型上界确定是Object

    总结一点就是:写入需要有类型下界,而读出需要有类型上界

    相关文章

      网友评论

        本文标题:JAVA泛型与类型安全

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