概述
在Java中,通配符类型List<?>等价于List<? extends Object>,List<? extends Animal>则代表其可以被赋值为List或List等其子类,List<? super Cat>代表其可以被List赋值。
对于采用extends形式的,主要用于读取(get),写入(set)就只能写入null
对于采用super形式的,主要用于写入,读取丢失了类型信息只能读取到Object,这就违背了泛型的初衷了。
前提
假设我们有以下代码,定义了各个类的继承顺序,本文主要探讨的是为什么Java要这样设计。
class Scratch {
public static void main(String[] args) {
}
static class Animal {
}
static class Cat extends Animal {
}
static class WhiteCat extends Cat {
}
static class Dog extends Animal {
}
}
在不使用泛型通配符的情况下,泛型之间是不存在继承性
考虑如下代码:
List<Cat> cats = new ArrayList<>();
List<Animal> animals = cats; //这句无法通过编译
为什么Java被设计成这样?
我们假设以上代码可以通过编译,那么我们可以构造如下代码:
List<Animal> animals = new ArrayList<>();
List<Object> objs = animals;
考虑我们使用List.add()时,就会发现一些不合理的部分了
objs.add(new Integer(1));
这显然不合理,原本用于存放Animal类型的List居然可以存放Integer,所以要禁止泛型间的继承性
使用泛型通配符时
extends限定
List<Cat> cats = new ArrayList<>();
List<? extends Animal> list = cats;
Animal animal = list.get(0);
list.add(null);
以上代码是可以通过编译的,我们可以看到,对于get,我们可以获取到的类型是Animal,这是因为所有的子类型都首先是个父类型,最终我们获取到的类型信息是父类类型。
为什么add不能有其它类型除了null
因为list无法确定原来的类型是什么,经历list=cats之后,list并不知道其持有的引用原来是保存Cat类型的,那能不能放入Animal类型呢,显然不行,一个Animal不一定是Cat,同理Animal的子类以及父类都不行,所以为了类型安全,那就啥类型也不然添加。
super限定
List<Animal> animals = new ArrayList<>();
List<? super Cat> list1 = animals;
list1.add(new WhiteCat());
Object object = list1.get(0);
以上代码也是可以通过编译的。对于add,我们可以添加任何继承Cat的类(同时也包括Cat本身),因为list1所指向的List其泛型可以是自身及其父类,add限制合情合理,添加的都是子类。
为什么get只能获得Object类型
其实获得Object类型就代表不知道其是什么类型。主要原因在于list1不知道原来泛型的类型是什么。
考虑极端情况list1 = new ArrayList(),这种情况下只能返回Object类型。
欢迎访问我的 个人网站(主要), Github, CSDN(主要), 博客园, 简书, 掘金, 知乎, 微信公众号:HelloVant(主要)