@version:1.1-RELEASE
本文目的解决:已知道有泛型,泛型统配符有什么用?是用来干嘛的?要怎么用?
为什么要存在泛型通配符
首先准备下面类图中的几个类便于理解。
- 在我们生活的真实世界中,如果一个盘子可以装水果,那么它就可以是一个装苹果的盘子,也可以是一个装橙子的盘子。
- 我们又已知Java三大特性之一的:多态。
通过上面两点,结合实际生活和代码知识,可以推导出以下表达式:
Plate<Fruit> plate = new Plate<Apple>();
但是可惜,编译无法通过。
编译器认为“苹果盘子 is a 水果盘子”这个表达式不成立。 苹果盘子 is not a 水果盘子。
但是下面这段代码却是成立的:
Fruit fruit = new Apple();
也就是说苹果和水果之间有继承关系是is a
的关系,但苹果盘子和水果盘子之间是没有继承关系的。
泛型通配符用来干嘛的
假设我有很多种的水果盘子,如果我想用一种统一的方式来“使用”这些盘子该怎么做呢?上面的例子已经说明水果盘子和苹果盘子之间无法赋值引用,Sun的大神们也同样碰到过这个问题,于是大神们引入了泛型统配符来达到这个目的。
下来来看这段代码:
Plate<? super Apple> applePlate1;
Plate<? extends Apple> applePlate2;
Plate<Food> foodPlate = new Plate<>();
Plate<Fruit> fruitPlate = new Plate<>();
Plate<Apple> applePlate = new Plate<>();
Plate<Orange> orangePlate = new Plate<>();
Plate<RedApple> redApplePlate = new Plate<>();
applePlate1 = foodPlate; // 成立
applePlate1 = fruitPlate; // 成立
applePlate1 = applePlate; // 成立
applePlate1 = orangePlate; // 错误
applePlate1 = redApplePlate; // 错误
applePlate2 = foodPlate; // 错误
applePlate2 = fruitPlate; // 错误
applePlate2 = applePlate; // 成立
applePlate2 = orangePlate; // 错误
applePlate2 = redApplePlate; // 成立
实践是检验真理的唯一标准。
通过上面的代码我们已经可以知道结果了,通过结果反推结论。
Plate<? super Apple>
:它是苹果盘子,也是水果盘子,也是食物盘子,是一种苹果的基类的盘子。Plate<? extends Apple>
:它是苹果盘子,也是红苹果盘子,是一种苹果的派生类的盘子。
<? super T> 表示是T或者T的基类
<? extends T> 表示是T或者T的派生类
下界和上界是怎么一回事
回到类图上,可以看到<? super Apple> 和 <? extends Apple> 在 Apple 所在的分支上,以 Apple 类为界线将这个类图分为两部分,有这么一个特点:以 Apple 作为下边界的是 Apple 的基类;以 Apple 作为上边界的是 Apple 的派生类。
在这里我们不提 Orange,因为两者都嫌弃它。
根据这个特点,我们将<? super T>
叫做下界,将<? extends T>
叫做上界。
借用网络上的一张图,可以更形象的表达下界、上界的含义。
上下边界带来的副作用
<? super T> 和 <? extends T> 除了所表示能接收的类范围不相同之外还有其他差别吗?
THOW ME THEN CODE!
Plate<? super Apple> applePlate1;
Plate<? extends Apple> applePlate2;
applePlate1.setItem(new Food()); // 错误
applePlate1.setItem(new Fruit()); // 错误
applePlate1.setItem(new Apple()); // 成立
applePlate1.setItem(new RedApple()); // 成立
applePlate1.setItem(new Orange()); // 错误
applePlate2.setItem(new Food()); // 错误
applePlate2.setItem(new Fruit()); // 错误
applePlate2.setItem(new Apple()); // 错误
applePlate2.setItem(new RedApple()); // 错误
applePlate2.setItem(new Orange()); // 错误
通过上面的代码可以发现:
- 对于下界,即被 <? super Apple> 修饰的:只要是边界(Apple)及以下的都可以放进去,边界以上的不行。
- 对于上界,即被 <? extends Apple> 修饰的:无论是边界的哪边都无法放进去。
由此可以总结出:
对于下界 <? super T> 可以放入T或T的派生类。 对于上界 <? extends T> 什么都不能放入。
继续!
Plate<? super Apple> applePlate1;
Plate<? extends Apple> applePlate2;
Food food1 = applePlate1.getItem(); // 错误
Fruit fruit1 = applePlate1.getItem(); // 错误
Apple apple1 = applePlate1.getItem(); // 错误
RedApple redApple1 = applePlate1.getItem(); // 错误
Orange orange1 = applePlate1.getItem(); // 错误
Food food2 = applePlate2.getItem(); // 成立
Fruit fruit2 = applePlate2.getItem(); // 成立
Apple apple2 = applePlate2.getItem(); // 成立
RedApple redApple2 = applePlate2.getItem(); // 错误
Orange orange2 = applePlate2.getItem(); // 错误
通过上面的代码可以发现:
- 对于上界,即被 <? extends Apple> 修饰的:只要是边界(Apple)及以上的都可以取出来。
- 对于下界,即被 <? super Apple> 修饰的:无论是边界的哪边都无法取出来。
由此可以总结出:
对于下界 <? super T> 不可以取。 对于上界 <? extends T> T或T的基类可以取出。
现象解释
对于下界 <? super T> 可以存T或T的派生类,但不能取; 对于上界 <? extends T> 不能存,但是可以从里面取出T或T的基类;
怎么解释这种现象呢?
在 Java 中有种现象叫“向上转型”,更细粒度的类可以由其基类直接接收,编译器会自动完成转型的过程。 Java 是单继承的,所有的继承类构成一个棵树,粒度粗的在上面,粒度细的在下面;相邻子树无法完成转型。
<? super T> 代表类的类型是 T 的基类,但不确定是 Class3 还是 Class2 或者 Class1(如上图),只能确定肯定是 T 的基类,所以只要是 T 的子类都可以放进去,因为编译器帮你完成了向上转型;但是不能取出来,因为无法确定 T 的基类到底是哪个(如果你用了 Class2 来接收实际的 Class1 ,向下转型无法由编译器自动完成),但因为所有的类都继承于 Object,所以还是可以使用 Object 来接收,但如果使用 Object 就失去泛型的意义了所以本帖不做说明。
<? extends T> 代表类的类型是 T 的派生类,T 的派生类有好多无法确定是哪个编译器无法完成自动转型(比如如果你想把 Class6 放进去,但实际类型是 Class5)所以无法放入;但可以确定的是里面的类型肯定是 T 的派生类所以可以使用 T 以及 T 的基类来接收。
泛型通配符的应用
上界不能往里面放,下界不能往外取。不放怎么取?不取放了有什么用?
根据上界、下界的特点,引出来一个原则:PECS。
如果你想从一个数据类型里获取数据(生产),使用 <? extends T> 通配符 如果你想把对象写入一个数据结构里(消费),使用 <? super T> 通配符 如果你既想存,又想取,那就别用通配符。
如:java.util.function.Function#andThen,入参类型是下界,返回值类型是上界;使用下界是使得对入参要求更严格,使用上界作为返回值类型放宽要求,通过两者共同保证方法的正确性、健壮性。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
特别说明
行尾注释是不规范的,在这个使用行尾注释只是因为语句没有复杂的含义,放在行尾能更好的表达正确还是错误。
参考资料
困扰多年的Java泛型 extends T> super T>,终于搞清楚了!
java 泛型中的上界(extend)和下界(super)
Java泛型之上、下边界通配符的理解(适合初学)
PECS法则与extends和super关键字
怎样理解java中Function<? super V, ? extends T>这样的声明?