泛型的逆变与协变 --《JAVA编程思想》 57

我们知道导出类可以向上转型为基类,如:Food 为 Fruit 的基类,则以下代码成立。

 Food food = new Fruit();

假设 Food 和 Fruit 同为 Container 类的泛型参数时,它们之间无法执行类型转换。

尽管持有 Fruit 的 Container 和 持有 Food 的 Container 两者之间存储的泛型参数有继承关系,但从容器类 Container 的角度出发,两个容器之间并无任何关联。

public class Food {
    
    }
public class Fruit extends Food{
    
    }
public class Container<T> {
    
    

    T item;

    public Container(T item) {
    
    
        this.item = item;
    }

    public T getItem() {
    
    
        return item;
    }

    public void setItem(T item) {
    
    
        this.item = item;
    }
    
}
public class ContainerTest {
    
    

    public static void main(String[] args) {
    
    
        Container<Fruit> fruitContainer = new Container<Fruit>(new Fruit());
        //无法通过编辑
        //Container<Food> foodContainer = fruitContainer;
    }

}

不过我们有时就是想在容器中存储的泛型之间建立继承关系,该怎么办呢?咱们接着往下看。

public class Food {
    
    }
public class Meat extends Food {
    
    }
public class Fruit extends Food {
    
    }
public class Apple extends Fruit {
    
    }
public class RedApple extends Apple {
    
    }

public class GreenApple extends Apple {
    
    }

上述类之间的继承关系如下图。
在这里插入图片描述

想在持有泛型的容器类之间建立继承关系,有两种方式,分别是协变逆变

根据上述各类之间的继承关系举例,<? extends Fruit> 可以帮助我们将泛型参数向上转换,此时泛型的边界为继承自 Fruit 类的任何类,可以是 Fruit ,也可以是 Apple 或者是 RedApple ,但具体是哪一种类型,无法确定,这种情形被称为协变,协变定义了泛型的上界

    public static void main(String[] args) {
    
    
        Container<? extends Fruit> fruitContainer = new Container<>();
        fruitContainer=new Container<Apple>();
        fruitContainer=new Container<RedApple>();
        //无法通过编译
        //fruitContainer=new Container<Food>();
    }

在这里插入图片描述

但协变后的泛型会失去写能力。这是因为无法确定<? extends Fruit> 中持有的是哪种具体类型,我们仅仅知道它源于 Fruit,很显然是无法将 GreenApple 塞入 RedApple 的容器中。

不过读能力不会受到影响,因为知道泛型的上界,便可以安全的向上转型为 Fruit 进行返回。

    public static void main(String[] args) {
    
    
        Container<? extends Fruit> fruitContainer = new Container<>();
        fruitContainer = new Container<Apple>();
        Fruit item = fruitContainer.getItem();
        //无论通过编译
        //fruitContainer.setItem(new Apple());
    }

协变相反的是逆变逆变能确定的泛型的下界<? super Fruit> 表示持有的都是 FruIt 或者 Fruit 的超类。

逆变会保留容器的写能力,丧失读能力

我们往容器内放入 Fruit 及其子类肯定是安全的,它们都可以向上转型为 Fruit。但是无法放入 Food ,因为 Food 可不一定是由 Fruit 转化而来,可能是 Meat ,故无法放入容器。

在读取容器持有类型时,因不能确定其上界,故只能将存储的元素作为 Object 类型取出,无法读取原本的具体类型。
在这里插入图片描述

    public static void main(String[] args) {
    
    
        Container<? super Fruit> fruitContainer = new Container<>();
        fruitContainer = new Container<Food>();
        fruitContainer.setItem(new Apple());
        fruitContainer.setItem(new RedApple());
        //无法通过编译
        //fruitContainer.setItem(new Food());
        Object item = fruitContainer.getItem();
    }

小结
1.协变用于定义泛型的上界,泛型失去写能力,变为只读。
2.逆变用于定义泛型的下界,泛型失去读能力,变为只写。

本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。

若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!

猜你喜欢

转载自blog.csdn.net/BaymaxCS/article/details/120519383