Java中的协变与逆变、泛型上下边界(extends、super)

目录

协变数组

Java泛型

普通泛型

通配符

上边界extends:协变

下边界super:逆变

 使用场合


协变与逆变是一个宽泛的概念,并不只存在于Java。

以经典的Animal、Cat为例

class Animal{
    @Override
    public String toString() {
        return "animal";
    }
}

class Cat extends Animal{
    @Override
    public String toString() {
        return "cat";
    }
}

Cat继承于Animal,Cat是Animal的子类型。Cat是对Animal功能的拓展,也就是说需要传入Animal的地方,也同样能够传入Cat。(里氏替换原则)

Cat与Animal如果单独拿出来用,继承关系非常明确。

但如果换一个环境,Cat[]与Animal[]是何关系? List<Cat>与List<Animal>是何关系?

协变和逆变就是用来描述这种本来满足继承关系的两个实体换一种更复杂的环境是否还满足继承关系。

如果依然满足原有继承关系,则称之为协变

如果继承关系(准确点说是兼容方向)发生了反转,则称之为逆变

如果转换后的两种结构没有任何关系,比如List<Animal>与List<Cat>互不兼容,则称之为不变

协变数组

Java中的数组是协变的

Object数组类型的引用可以指向Animal数组,Animal类型的数组可以存放Cat。

public class ArrTest {

    public static void main(String[] args) {
        Object[]arr = new Animal[2];
        arr[0] = new Animal();
        arr[1] = new Cat();

        for (int i = 0; i < arr.length; i++) {
            System.out.println(i + " : " + arr[i]);
        }
        arr = new Cat[0];
    }

}

但如果尝试用Cat[ ] 类型的数组接收Animal对象,则会抛出异常ArrayStoreException。

public class ArrTest {

    public static void main(String[] args) {
        Object[]arr = new Cat[2];
        arr[0] = new Animal();
        arr[1] = new Cat();

        for (int i = 0; i < arr.length; i++) {
            System.out.println(i + " : " + arr[i]);
        }
    }

}

Java泛型

普通泛型

普通泛型是不可变的

Java中的泛型信息在编译期间会被擦除,上述的写法无法通过语法检查。

通配符

泛型看似好像将协变与逆变挡在了门外,但其实并不是一刀切,我们依然可以通过上下边界来拓展继承关系。

上边界extends:协变

如果我们需要保持继承关系,即List<Animal>可以接收List<Cat>之类的子类型集合。

 可以看到,我们尝试向集合里添加数据的时候,都失败了,哪怕是最基本的Animal对象也不可以。这是为啥?

Animal的子类可能有Cat、Dog。Cat可以兼容Animal,Dog可以兼容Animal,但是问题在于Dog与Cat之间互不兼容,一个集合中可能出现两种甚至更多种类型的对象,是不安全的。所以干脆都不让添加元素了。

那这个上边界还有意义吗?

有,只要你能证明你传递的集合都是同一种类型的对象就可以了。看上图下方的两个例子,一个是单纯的赋值,另一个是方法传参。共同点在于我传递的是Cat类型的集合,确保集合里是同一种类型的对象,且Cat继承于Animal。

? extends Animal 表达的是,能从这个集合里面获取到的,都是Animal的子类。

下边界super:逆变

super有一个地方不是很好理解,网上很多的博客在这个地方都写的有点矛盾。

 首先,<?super Cat >作为下边界, 表示的是只能接收Cat以及Cat父类的拓展数据类型的引用

特地强调是拓展数据类型的引用,就拿集合来说

 我向集合里添加了Cat的父类Animal,以及子类SmallCat,结果添加父类的时候报语法错误,子类却没事,说好的下边界呢?说好的只能添加Cat及Cat父类呢?

其实协变和逆变都只是针对于List<Animal>、List<Cat>这些拓展类型来说的,Animal与Cat本身的继承关系不受影响,依然是Cat继承于Animal。

看后面的引用、参数传递,就知道super的具体作用了。

List<? super Cat>  类型的引用,只能指向 List<? super Animal>以及 List<? super Cat>类型的数据结构,无法指向 List<? super SmallCat>类型。

这才所谓的下边界。

SmallCat类型是Cat的子类型,所以Cat类型的引用可以指向SmallCat对象

但是使用泛型后,List<? super Cat>类型的引用,就不能指向List<SmallCat>类型的对象,但是可以指向List<Animal>集合,这就是继承关系的倒置,也就是“逆变”

另外,使用super时,尝试获取容器内部元素时,默认的类型是Object,因为Cat最保险最顶层的基类就是Object。所以说使用super时,放便添加,但是不利于读取

 使用场合

总结一下上边界与下边界的优缺点

使用上边界(extends)时,无法添加元素,但是可以轻易的获取元素。(适合 读取

使用下边界(super)时,可以很容易的添加元素,但是获取元素的时候只能用Object类型的引用接收。(适合写入

其实也就是PECS原则Producer Extends Consumer Super),生产者适合用extends,消费者适合用super。

Jdk源码中找一找也有类似的应用,例如Collection的一个集合拷贝方法

src 起始集合,也就是输入。

dest 目标集合,也就是输出。

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }

如有错误,欢迎批评指正。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Java泛型协变逆变都是针对类型转换的规定。 协变(covariant):指的是继承链子类(派生类)类型能够作为父类(基类)类型的一种属性,也就是子类可以作为父类使用的能力。在泛型协变的概念可以用来表示如果类型A是类型B的一个子类型,那么泛型类G<A>就可以视作泛型类G<B>的一个子类型。 例子: ```java // Animal类 public class Animal {} // Dog类是Animal类的子类 public class Dog extends Animal {} // 泛型接口List public interface List<E> { void add(E e); E get(int index); } // 定义一个方法acceptList,其形参类型为List<? extends Animal> public static void acceptList(List<? extends Animal> list) { for (Animal animal : list) { // ... } } // List类型为List<Dog> List<Dog> list = new ArrayList<Dog>(); list.add(new Dog()); acceptList(list); // 在这里,我们可以传入一个List<Dog>参数,因为Dog类是Animal类的子类 ``` 逆变(contravariant):指的是继承链父类(基类)类型能够作为子类(派生类)类型的一种属性,也就是父类可以作为子类使用的能力。在泛型逆变的概念可以用来表示如果类型A是类型B的一个超类型,那么泛型类G<B>就可以视作泛型类G<A>的一个子类型。 例子: ```java // Animal类 public class Animal {} // Dog类是Animal类的子类 public class Dog extends Animal {} // 泛型接口Comparator public interface Comparator<T> { int compare(T o1, T o2); } // 定义一个方法sortList,其形参类型为List<? super Dog> public static void sortList(List<? super Dog> list) { // ... } // List类型为List<Animal> List<Animal> list = new ArrayList<Animal>(); list.add(new Animal()); sortList(list); // 在这里,我们可以传入一个List<Animal>参数,因为Animal类是Dog类的超类型 ``` extendssuper关键字常常用于定义泛型类型参数的上边界(upper bound)和下边界(lower bound)。extends表示类型参数的上限,超过这个范围就会导致编译错误;super表示类型参数的下限,超过这个范围也会导致编译错误。 例子: ```java // 泛型类Pair,其类型参数T有上限(用extends)为Comparable<? super T>,表示类型T要么是Comparable<? super T>本身,要么是Comparable<? super T>的子类型 public class Pair<T extends Comparable<? super T>> { private T first; private T second; public Pair(T first, T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public T max() { return first.compareTo(second) >= 0 ? first : second; } } // Pair类型为Pair<String> Pair<String> pair = new Pair<String>("hello", "world"); String max = pair.max(); // 在这里,我们可以调用max方法,因为String类实现了Comparable<String>接口 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值