public class TestType {
public static void main(String[] args) {
//? extend B,在定义泛型时,只能接受B或B的子类
List extends B> list = new ArrayList();//编译通过
list = new ArrayList(); //编译通过
//list = new ArrayList(); //报错,编译不通过
//除了null外,无论往集合中插入什么元素都报错,因为 extends E>只是告诉编译器集合中元素类型上限,
//但它具体什么类型编译器不知道,也就是说它的类型是不确定的,既然不确定,为了类型安全,编译器只能阻止添加元素了
//list.add(new A());//错误,编译不通过
//list.add(new B());//错误,编译不通过
list.add(null);
//? super B,在定义泛型时,只能接受B或B的父类
List super B> list1 = new ArrayList(); //编译通过
list1 = new ArrayList(); //编译通过
//list1 = new ArrayList(); //编译不通过
//在add插入元素时,由于我们知道了集合中装的元素是B或B的父类,我们无法确定是具体哪个类型,
//但是我们可以确定的是B和B的子类和“这个不确定类”是兼容的,因为它肯定是这个“不确定类”的子类,
//所以我们可以往集合中添加B或B子类的对象
list1.add(new C()); //编译通过
list1.add(new B()); //编译通过
//list1.add(new A()); //错误,编译不通过
}
}
class A{
}
class B extends A{
}
class C extends B{
}
? extend E : 指的是E及其E的子类,指定了泛型的上限(E),由于E是该范围内最上面的类,所以在list.add(具体类)是不可以的,因为只知道泛型的上限,但具体添加的对象和 list = new ArrayList<>(),中的类不一定匹配,会出现类型转换问题,所以干脆不可以添加。
extends E> 在定义泛型时,只能接受E或E的子类,在往集合中插入元素时,由于 extends E>只是告诉编译器集合中元素类型上限,但它具体什么类型编译器不知道,也就是说它的类型是不确定的,既然不确定,为了类型安全,编译器只能阻止添加元素了
补充:> 是 extends Object>的简写
? super E : 指的是E及其E的父类,指定了泛型的下限(E),由于指定了泛型的下限E,所以添加的对象只能是E或者E的子类,这样就不会出现类型转换错误,这就可以添加了,编译成功。
比如: List super Apple> apples; 表示集合中元素类型下限为Apple类型,即只能是Apple或Apple的父类,因此对于下面的赋值是合理:
apples = new ArrayList();
apples = new ArrayList();
apples = new ArrayList();
如果元素类型为Apple的子类,则编译不同过 :apples = new ArrayList(); //编译不通过
再看看 super E>限定的集合的读写操作
写入
因为 apples中装的元素是Apple或Apple的某个父类,我们无法确定是哪个具体类型,但是可以确定的是Apple和Apple的子类是和这个“不确定的类”兼容的,因为它肯定是这个“不确定类型”的子类,也就是说我们可以往集合中添加Apple或者Apple子类的对象,所以对于下面的添加是允许的 :
apples.add(new Apple());
apples.add(new RedApple());
读取
编译器允许从apples中获取元素的,但是无法确定的获取的元素具体是什么类型,只能确定一定是Object类型的子类,因此我们想获得存储进去的对应类型的元素就只能进行强制类型转换了
Apple apple = (Apple)apples.get(0);//获取的元素为Object类型
问题来了,JDK1.5引入泛型的目的是为了避免强制类型转换的繁琐操作,那么使用泛型 super E>干嘛呢?我们可以使用 super E>写入数据。
PECS法则
那么这里,我们可以总结一条规律,“Producer Extends,Consumer Super”,意思是:
Producer Extends:如果你需要一个只读的List,用它来produce T,那么使用 ? extends T
Consumer Super:如果你需要一个只写的List,用它来consumer T,那么使用 ? super T
如果需要同时读取和写入,那么就不能使用通配符了。
Java集合类的源码中,经常有将两者结合起来使用的,如:
public class Collections{
public static 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
dest.set(i, src.get(i));
} else {
ListIterator super T> di=dest.listIterator();
ListIterator extends T> si=src.listIterator();
for (int i=0; i
di.next();
di.set(si.next());
}
}
}
}