问题引入:
// compile error// List <? extends Fruit> appList2 = new ArrayList();// appList2.add(new Fruit());// appList2.add(new Apple());// appList2.add(new RedApple());List <? super Fruit> appList = new ArrayList();appList.add(new Fruit());appList.add(new Apple());appList.add(new RedApple());
看到这个问题,打开eclipse写代码验证一下,得出以下一些结论:
首先解释两个概念:Java中的通配符和边界 通配符: “?”就是一个占位符,它不表示任何具体的类型,而是表示符合定义规则的一个或者多个类型的一个占位标志 边界: <? extends T> 表示上界通配符 它表示T以及T的子类, 类型最高是T <? super T> 表示下界通配符 它表示T以及T的超类,类型最高可到Object ,最低是T
<? extends T> 表示上界通配符 它表示T以及T的子类, 类型最高是T
为什么上述问题中使用上界通配符后就出现编译出错的问题: List<? extends Fruit> list 表示当前列表中可以存储 Fruit以及Fruit的子类, 从这层含义来看,似乎上述不应该报错,因为存储的对象是符合list定义限定数据范围的。 但是事实上确实报错了,这主要是Java出于泛型安全性的考虑。因为上界通配符<? extends T> 表示的是一个类型范围,编译器无法确定List所持有的具体类型,所以无法安全的向其中添加对象。但是可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List<Apple> 赋值。因此对于上界边界符,Java编译器限制了其存数据。即:<? extends T> 不能存数据,但是可以取数据,而使用get方法取出来的数据只能赋值为T类型的变量。T value = list.get(0);
<? super T> 下界通配符: 表示T以及T的超类,可以存数据,但是只能取出Object的数据 如: List<? super apple> list = new ArrayList<fruit>();; list.add(new apple()); list.add(new redApple()); // list.add(new fruit()); 此处编译报错,
出错分析: 因为list的定义限定存储数据的类型最低是apple,因此对于编译器来说,只有存入低于或者等于apple的数据对象才能认为是数据安全的。所以存入fruit对象时才会报错。
而同样的 如果要在当前list中取出数据时,因为限定的是最低类型,而最高可以达到Object类,所以,在get数据时只能赋值给Object对象。(假设Java中没有Object类的定义,这里也许就不被允许get数据)
以上分析了泛型边界情况在容器中的使用,同样的在类定义中使用的泛型也同样受到相同的限制。 如:定义个盘子类, public static class Plate<T> { T item; public Plate(T t) { item = t; } public void set(T t) { item = t; } public T get() { return item; } }
写法1: Plate<? extends fruit> plate = new Plate<apple>(new apple()); plate.set(new redApple()); // 编译报错 plate.get();
报错分析:因为plate定义为<? extends fruit>类型,因此不能往里存而只能往外读,所以第二句报错。而第三句没有问题。 但是刚开始出现了一个疑问,为什么第一句构造函数同样是赋值却可以编译通过呢,原因:第一句构造的过程制定了具体的类型“apple” 所以当前构造出来的具体对象是一个放apple的盘子, 而把这个有具体类型的对象赋值给了一个没有具体类型的变量。 所以有具体类型的对象可以写,而没有具体类型的变量却不能够进行写操作。
写法2: Plate<? super apple> plate = new Plate<apple>(new apple()); plate.set(new fruit()); // 编译报错 plate.set(new redApple()); plate.set(new apple()); plate.get().show(); // 编译报错
上述写法中有两处报错的地方 第二句报错的原因:plate限定的类型最低是apple类型,而这里却要存放一个apple的父类fruit 检测到类型不安全,所以报错。 第五句报错原因:对于下边界符的限定 不影响写却影响读,且读获取到的只是Object对象,如果在第五句加一个强制转换,则是可以正常编译且得出正确结果的。