我们知道导出类可以向上转型为基类,如: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.逆变用于定义泛型的下界,泛型失去读能力,变为只写。
本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!