<? extends T> 及<? super T> 重温
编码例子背景
import com.google.common.collect.Lists; //引入guava的Lists工具方便生产List实例
class Food {
/** name*/
protected String name = "Food";
/** 打印食物名称*/
public void echo() {
System.out.println(name);
}
}
class Fruit extends Food {}
class Apple extends Fruit {
public Apple() {
name = "Apple";
}
}
class Pear extends Fruit {
public Pear() {
name = "Pear";
}
}
class Plate<T> {
private T item;
public Plate() {}
public Plate(T item) {
this.item = item;
}
public void set(T t) {
item = t;
}
public T get() {
return item;
}
/** 模仿处理泛型T实例*/
public void methodWithT(T t) {
//简单打印实例
System.out.println("methodWithT,T's is : " + t);
}
}
引出问题背景
Fruit fruit = new Fruit();
Apple apple = new Apple();
Plate<Fruit> fruitPlate = new Plate<>();
Plate<Apple> applePlate = new Plate<>();
Fruit fruit = new Fruit();
Apple apple = new Apple();
// 父类容器,放置子类
// 此处是多态的提供的API
// apple is a fruit
fruitPlate.set(apple);
fruitPlate.methodWithT(apple);
// 父类容器引用指向(被赋值)子类容器
// 父类容器与子类容器的关系,此处关注的是容器Plate这个类!!!
// 并没有像父子类中的继承关系(多态)!!!
// apple's plate is not a fruit's plate
// ERROR
// fruitPlate = applePlate; // 装水果的盘子无法指向装苹果
// ERROR
// applePlate = fruitPlate;// 明显错误,子类容器指向父类容器
<? extends T>
示例如下:
/**
* <? extends T> 上界通配符(Upper Bounds Wildcards)
* <p>
* Plate <? extends Fruit> extendsFruitPlate 可以被 Plate <Fruit> 及 Plate <Apple> 赋值
*/
@Test
public void extendsTest() {
Fruit fruit = new Fruit();
Apple apple = new Apple();
Plate<Fruit> fruitPlate = new Plate<>();
Plate<Apple> applePlate = new Plate<>();
Plate<? extends Fruit> extendsFruitPlate ;
// SUCCESS
// Plate<? extends Fruit>引用可以指向Plate<Fruit>以及Plate<Apple>
extendsFruitPlate = applePlate;
extendsFruitPlate = fruitPlate;
}
<? supper T>
示例如下:
/**
* <? supper T> 下界通配符(Lower Bounds Wildcards)
* Plate <? supper Fruit> superFruitPlate 可以被 Plate <Fruit> 及 Plate <Object> 赋值
*/
@Test
public void supperTest() {
Fruit fruit = new Fruit();
Apple apple = new Apple();
Plate<Fruit> fruitPlate = new Plate<>();
Plate<Apple> applePlate = new Plate<>();
Plate<Object> objectPlate = new Plate<>();
Plate<? super Fruit> superFruitPlate = new Plate<>();
// SUCCESS
// Plate<? super Fruit>引用可以指向Plate<Fruit>以及Plate<Object>
superFruitPlate = fruitPlate;
superFruitPlate = objectPlate;
// ERROR
// superFruitPlate = applePlate; // <? supper Fruit>修饰的容器不能被子类容器赋值
}
以下说明是相关调整的表现及这样的限定原因
<? extends T> 修饰的泛型其接口特点
<? extends T> 具体表现:
返回父类T的接口
:调用这类型的接口返回的实例类型是父类T(这句结论说跟没说一样,理解起来特别容易.)接收父类T的接口
:这类型的接口均不可以再被调用
代码表现如下:
/**
* <? extends T> 上界通配符(Upper Bounds Wildcards)
* 注意点: 只能获取,不能存放
*/
@Test
public void extendsAttentionTest() {
Fruit fruit = new Fruit();
Apple apple = new Apple();
Pear pear = new Pear();
Plate<? extends Fruit> extendsFruitPlate;
extendsFruitPlate = new Plate<Fruit>(fruit);
extendsFruitPlate = new Plate<Apple>(apple);
extendsFruitPlate = new Plate<Pear>(pear);
// 以下ERROR代码,尝试调用接收泛型T的方法,均编译不过
// ERROR:
// extendsFruitPlate.set(fruit);
// extendsFruitPlate.set(apple);
// extendsFruitPlate.set(pear);
// extendsFruitPlate.set(new Object());
// extendsFruitPlate.methodWithT(fruit);
// extendsFruitPlate.methodWithT(apple);
// extendsFruitPlate.methodWithT(pear);
// extendsFruitPlate.methodWithT(new Object());
// 以上注释的错误代码,初学者也会有疑问,
// 为什么<Plate<? extends Fruit> extendsFruitPlate;这样装水果子类的盘子,
// 现在什么东西都不能放了?那我还要这个容器有什么用?这是不是跟思维惯性认知有点偏差?
// SUCCESS
// 返回的是泛型T即Fruit,具体的实例是Pear类型
Fruit getFruit = extendsFruitPlate.get();
getFruit.echo();// 输出Pear
// 接口测试
class ExtendsClass {
public void extendsMethod(List<? extends Fruit> extendsList) {
// ERROR:
// 出错原理同上,不能调用接收泛型T的方法
// extendsList.add(fruit);
// extendsList.add(apple);
// extendsList.add(new Object());
// SUCCESS
// 获取是父类,可以强转为子类再使用
Fruit getFruitByList = extendsList.get(0);
getFruitByList.echo();
}
}
List<Fruit> fruits = Lists.newArrayList(fruit);
List<Apple> apples = Lists.newArrayList(apple);
ExtendsClass extendsClass = new ExtendsClass();
// List<? extends Fruit> extendsList可以接收List<Fruit>/List<Apple>
extendsClass.extendsMethod(fruits);
extendsClass.extendsMethod(apples);
}
<? extends T> 相关限制的原因
Fruit fruit = new Fruit();
Apple apple = new Apple();
Pear pear = new Pear();
Plate<? extends Fruit> extendsFruitPlate;
extendsFruitPlate = new Plate<Fruit>(fruit);
extendsFruitPlate = new Plate<Apple>(apple);
extendsFruitPlate = new Plate<Pear>(pear);
编译器的理解: Plate<? extends Fruit> extendsFruitPlate 这个盘子 :
- 你不能保证读取到 Apple ,因为 extendsFruitPlate 可能指向的是
Plate<Fruit>
- 你不能保证读取到 Pear ,因为 extendsFruitPlate 可能指向的是
Plate<Apple>
- 你可以读取到 Fruit ,因为 extendsFruitPlate 要么包含 Fruit 实例,要么包含 Fruit 的子类实例.
- 你不能插入一个 Fruit 元素,因为 extendsFruitPlate 可能指向
Plate<Apple>
或Plate<Pear>
- 你不能插入一个 Apple 元素,因为 extendsFruitPlate 可能指向
Plate<Fruit>
或Plate<Pear>
- 你不能插入一个 Pear 元素,因为 extendsFruitPlate 可能指向
Plate<Fruit>
或Plate<Apple>
你不能往Plate<? extends T>
中插入任何类型的对象,因为你不能保证列表实际指向的类型是什么,
你并不能保证列表中实际存储什么类型的对象,唯一可以保证的是,你可以从中读取到T
或者T
的子类.
所以,
- 可以调用接收泛型T的方法的接口 extendsFruitPlate.get() 获取 Fruit 的实例
- 却不能调用接收泛型T接口 extendsFruitPlate.set(fruit)添加任何元素
- 也不能调用接收泛型T接口 extendsFruitPlate.methodWithT(fruit)处理任何对象
Apple apple = new Apple();
Plate<Pear> pearPlate=new Plate<Pear>();
// 以下明显类型不相同,且不符合多态,导致类型转换异常
pearPlate.set(apple);
pearPlate.methodWithT(apple);
<? super T> 修饰的泛型其接口特点
<? super T> 具体表现
返回父类T的接口
:调用这类型的接口返回的类型是Object类型接收父类T的接口
:调用这类型的只能传入父类T及T的子类实例
代码表现如下:
/**
* <? supper T> 下界通配符(Lower Bounds Wildcards)
* 注意点: 取出是Object,存放是父类或子类
*/
@Test
public void superAttentionTest() {
Object object = new Object();
Food food = new Food();
Fruit fruit = new Fruit();
Apple apple = new Apple();
Pear pear = new Pear();
Plate<? super Fruit> superFruitPlate;
// 可以被 Plate<Object> , Plate<Food> ,Plate<Fruit> Plate<父类容器> 赋值
superFruitPlate = new Plate<Object>(object);
superFruitPlate = new Plate<Food>();
superFruitPlate = new Plate<Fruit>();
// SUCCESS
superFruitPlate.set(fruit);
superFruitPlate.set(apple);
superFruitPlate.set(pear);
superFruitPlate.methodWithT(fruit);
superFruitPlate.methodWithT(apple);
superFruitPlate.methodWithT(pear);
// ERROR:接收父类T的接口,当[不是 T或T的子类时],则编译异常
// superFruitPlate.set(food);
// superFruitPlate.set(object);
// superFruitPlate.methodWithT(food);
// superFruitPlate.methodWithT(object);
// 以上注释的错误代码,初学者也会有疑问,
// 为什么<Plate<? super Fruit> superFruitPlate;这样可以指向水果父类的盘子,
// 现在却只能放子类?这是不是跟思维惯性认知有点偏差?
// 只能获取到Object对象,需要进行强转才可以进行调用相关API
Object supperFruitPlateGet = superFruitPlate.get();
if (supperFruitPlateGet instanceof Fruit) {
// 为什么需要 instanceof ?
// superFruitPlate可以指向Plate<Food>,获取出来实际是Food实例
Fruit convertFruit = (Fruit) supperFruitPlateGet;
convertFruit.echo();
}
// 接口测试
class SuperClass {
public void supperMethod(List<? super Fruit> superList) {
superList.add(fruit);
superList.add(apple);
superList.add(pear);
// ERROR:原因如上,调用method(T t)时候,当t[不是 T或T的子类时],则编译异常
// superList.add(object);
// superList.add(food);
Object innerObject = superList.get(0);
if (innerObject instanceof Fruit) {
// 为什么需要 instanceof ?
// 像这样:superFruitPlate 可以指向List<Object> objects,获取出来是Object
Fruit innerConvertFruit = (Fruit) innerObject;
innerConvertFruit.echo();
} else {
System.out.println("supperMethod:非Fruit,插入非本类或非子类:" + innerObject);
}
}
}
List<Object> objects = new ArrayList<>(Arrays.asList(object));
List<Food> foods = new ArrayList<>(Arrays.asList(food));
List<Fruit> fruits = new ArrayList<>(Arrays.asList(fruit));
List<Apple> apples = new ArrayList<>(Arrays.asList(apple));
List<Pear> pears = new ArrayList<>(Arrays.asList(pear));
SuperClass superClass = new SuperClass();
superClass.supperMethod(objects);
superClass.supperMethod(foods);
superClass.supperMethod(fruits);
// ERROR 原因同上,非Fruit及Fruit父类容器则编译不通过
// superClass.supperMethod(apples);
// superClass.supperMethod(pears);
}
<? super T> 相关限制的原因
Plate<? super Fruit> superFruitPlate;
// 可以被 Plate<Object> , Plate<Food> ,Plate<Fruit> Plate<父类容器> 赋值
superFruitPlate = new Plate<Object>(object);
superFruitPlate = new Plate<Food>();
superFruitPlate = new Plate<Fruit>();
编译器的理解: Plate<? super Fruit> superFruitPlate 这个盘子 ,
- superFruitPlate 不能确保读取到 Fruit ,因为 superFruitPlate 可能指向
Plate<Object>
或Plate<Food>
- superFruitPlate 不能确保读取到 Food ,因为 superFruitPlate 可能指向
Plate<Object>
所以,取出来必须是Object
,最后需要则调用强转
- 你不能插入一个 Food 元素,因为 superFruitPlate 可能指向
Plate<Fruit>
- 你不能插入一个 Object 元素,因为 superFruitPlate 可能指向
Plate<Fruit>
或Plate<Food>
- 你可以插入一个 Fruit/Apple/Pear 类型的元素,因为 Fruit/Apple/Pear 类都是 Fruit,Food,Object的本类或子类
所以,从 superFruitPlate 获取到的都是Object对象,superFruitPlate 插入的都是Fruit的本类或本身
故有如下结论:
- superFruitPlate 调用返回父类T的接口,获取到的都是 Object 对象;
- superFruitPlate 调用接收父类T的接口,只能传入父类T及T的子类实例
Plate<Fruit> pearPlate=new Plate<Fruit>();
Food food=new Food();
// 以下明显类型不相同,且不符合多态,导致类型转换异常
fruitPlate.set(food);
fruitPlate.methodWithT(food);
PECS (Producter Extends, Consumer Super) 原则
以上原则来源两者主要区别,合理使用其优点,有种去其糟粕,取其精华的意思
<? extends T>
: 可以获取父类,向外提供内容,为生产者角色
<? super T>
: 可以调用接收/处理父类及子类的接口,为消费者角色
举一个JDK 中例子:
public static <T> 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<srcSize; 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<srcSize; i++) {
di.next();
// si 来源List<? super T> dest,向外提供内容,生产者
// di 来源List<? extends T> src,接收/处理类型T的本类或子类,消费者
di.set(si.next());
}
}
}
小结
至此,本文完结了.重温的时候,发现很多之前想当然的结论,并没有细细研究其中原因.现在理解起来是不会再次忘记的了.要记住的是需要理解编译器是怎么认为的而不是怎么从修饰符去片面理解
通篇显得有点啰嗦,至少作者认为把重点及原因说清楚了.以上如有不当之处敬请指正.
参考
本文例子来源主要有二,最精髓的地方是StackOverflow的链接的第一第二个回答
博客园 : RainDream : <? extends T>和<? super T>
Stackoverflow:Difference between<? super T>and<? extends T>in Java