文章目录:
1.简介
<? extends T>
和<? super T>
是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。
- <? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”
- <? super T>:是指 “下界通配符(Lower Bounds Wildcards)”
为什么要用通配符和边界?
使用泛型的过程中,经常出现一种很别扭的情况。比如我们有Fruit类,和它的派生类Apple类。
class Fruit {}
class Apple extends Fruit {}
然后有一个最简单的容器:Plate类。盘子里可以放一个泛型的“东西”。我们可以对这个东西做最简单的“放”和“取”的动作:set( )和get( )方法。
class Plate<T> {
private T item;
public Plate(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。
Fruit fruit = new Apple(); //正确
Plate<Fruit> p = new Plate<Apple>(new Apple()); //报错了
但实际上Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”。
实际上,编译器脑袋里认定的逻辑是这样的:
- 苹果 IS-A 水果
- 装苹果的盘子 NOT-IS-A 装水果的盘子
所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的。所以我们不可以把Plate的引用传递给Plate。
为了让泛型用起来更舒服,Sun公司的那些大牛们就想出了
<? extends T>
和<? super T>
的办法,来让”水果盘子“和”苹果盘子“之间发生关系。
2.<? extends T>上界通配符
下面代码就是“上界通配符(Upper Bounds Wildcards)”:
Plate<? extends Fruit>
翻译成人话就是:一个能放水果以及一切是水果派生类的盘子。再直白点就是:啥水果都能放的盘子。这和我们人类的逻辑就比较接近了。
Plate<? extends Fruit>
和Plate<Apple>
最大的区别就是:Plate<? extends Fruit>
是Plate<Fruit>
以及Plate<Apple>
的基类。直接的好处就是,我们可以用“苹果盘子”给“水果盘子”赋值了。Plate<? extends Fruit> p = new Plate<Apple>(new Apple());
假设,我有下面这样的继承结构:↓↓↓
class Food {}
class Fruit extends Food {}
class Meat extends Food {}
class Apple extends Fruit {}
class Banana extends Fruit {}
class Beef extends Meat {}
class Pork extends Meat {}
class RedApple extends Apple {}
class GreenApple extends Apple {}
此时,上面的 <? extends Fruit> 就表示下图的红色部分。
class MainTest1 {
public static void main(String[] args) {
Plate<? extends Fruit> plate = new Plate<>(new Apple());
//不能存入任何元素
// plate.setItem(new Fruit());
// plate.setItem(new Apple());
//读取出来的东西只能存放在Fruit或它的基类里
Fruit fruit = plate.getItem();
Food food = plate.getItem();
Object obj = plate.getItem();
//Apple apple = plate.getItem(); //报错
}
}
但是呢,在上界通配符中存在着一定的缺陷:不能往里存,只能往外取。
<? extends Fruit>会使往盘子里放东西的set()方法失效。但取东西get( )方法还有效。比如下面例子里两个set()方法,插入Apple和Fruit都报错。
- setItem() : 编译器只知道元素类型是 Fruit 或 Fruit 的子类,所以有可能是 Fruit、Apple、Banana、RedApple、GreenApple中的某一个类型,那么当我们向其中添加元素时,编译器并不知道具体是哪一个 (派生) 类。所以这种操作是不允许的。
- getItem() : 即使编译器不知道此时类、集合中的元素是 Fruit、Apple、Banana、RedApple、GreenApple 中的哪一个,但是能够确定的是这些元素都是由 Fruit 派生出来的,所以上面的代码中 getItem 方法是可以用 Fruit、Fruit的父类Food、总类Object 接收。
3.<? super T>下界通配符
下面代码就是“下界通配符(Upper Bounds Wildcards)”:
Plate<? super Fruit>
表达的就是相反的概念:一个能放Fruit以及一切是Fruit基类的盘子。
Plate<? super Fruit>
是Plate<Fruit>
的基类,但不是Plate<Apple>
的基类。对应刚才那个例子,Plate<? super Fruit>
覆盖下图中红色的区域。Plate<? super Fruit> plate = new Plate<>(new Fruit());
假设,我有下面这样的继承结构:↓↓↓
class Food {}
class Fruit extends Food {}
class Meat extends Food {}
class Apple extends Fruit {}
class Banana extends Fruit {}
class Beef extends Meat {}
class Pork extends Meat {}
class RedApple extends Apple {}
class GreenApple extends Apple {}
此时,上面的 <? superFruit> 就表示下图的蓝色部分。
class MainTest2 {
public static void main(String[] args) {
Plate<? super Fruit> plate = new Plate<>(new Fruit());
//存入元素正常
plate.setItem(new Fruit());
plate.setItem(new Apple());
//读取出来的东西只能存放在Object类中
//Apple apple = plate.getItem();
//Fruit fruit = plate.getItem();
Object obj = plate.getItem();
}
}
但是呢,在下界通配符中存在着一定的缺陷:下界<? super T>可以往里存,但往外取只能放在Object对象里。
使用下界<? super Fruit>会使从盘子里取东西的get()方法部分失效,只能存放到Object对象里。set( )方法正常。
- setItem() : 编译器只知道元素类型是 Fruit 或者 Fruit 的基类或父类,所以有可能是 Fruit、Food、Object 其中的一个类型。编译器知道类型的下界是 Fruit ,根据类型向上兼容的特性,所以可以添加的元素是 Fruit 以及 Fruit 的派生类。
- getItem() : 既然编译器不确定集合类型是 Fruit 或者 Fruit 的基类或父类中的哪一种,可能是Fruit,可能是Food,那么返回类型只能是它们的共同父类 Object。但是这里可以通过强制类型转换成相应的Fruit的子类。
4.简单小总结
最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:
- <? extends C> 适合大量做获取操作的情景。
- <? super C> 适合大量做添加操作的情景。
特点:<? extends C> 的 add() 被限制,<? super C> 的 get() 被限制。