Java 泛型 ? extends 与 ? super
我们经常在集合的泛型中用到 extends、super 关键字。先看下 List 集合中获取和放入接口的定义:
通过类定义可以看到,泛型的具体类型在创建集合实例时指定,用于限定该实例的 get/set 取出和放入时的集合元素类型。
- List<? extends T>:声明上界,表示参数化的类型可能是所指定的 T 类型,或者是此类型的任意子类型。最终子类型:未知。
- List<? super T>:声明下界,表示参数化的类型可能是所指定的 T 类型,或者是此类型的任意父类型。最终父类型:已知——Object。
- Java 中泛型不变:假设有 A extends B,但 List<A> 和 List<B> 不存在型变关系。
泛型的简单使用
了解上述后,再看下面你就不会觉得奇怪。
- 泛型不变
class A { }
class B extends A { }
List<A> list1 = new ArrayList<A>(); // work 泛型不变
list1.add(new A()); // work
list1.add(new B()); // work
A a = list1.get(1); // work
List<A> list2 = new ArrayList<B>(); // 编译错误,泛型不变,也就不支持协变(类似多态)
集合可读可写,集合泛型不变。
class A { }
class B extends A { }
List<? extends A> list1 = new ArrayList<B>(); // 协变——父类引用指向子类
list1.add(new Object()); // 错误,容器不可写,不能放入任何值(null 除外)
A a = list1.get(1); // work 可读,且有泛型
集合可读、不可写,集合泛型协变。
class A {}
class B extends A {}
List<? super B> list = new ArrayList<A>(); // 逆变——子类引用指向父类
list.add(new A()); // 编译错误,集合中放入的元素类型只能为 B 及 B 子类型
list.add(new B()); // work
Object b = list.get(0); // work 可读,但无类型都是 Object
集合可读 Object 、可写,集合泛型逆变。
小结
在上面的例子中,我们至少能看出:
- ? extends T 针对返回值泛型使用(如,只读的消费者集合泛型),指定的 T 为集合元素的通用父类型,用于限定取出类型为 T 的子类型、打破泛型不变。
- ? super T 针对方法参数泛型使用(如,只写的生产者集合泛型),指定的 T 为集合元素的通用父类型,用于限定放入类型为 T 的子类型、打破泛型不变。
extends 与 super 互补。
extends 用于方法返回值,super 用于方法参数。即,我们所说的 PECS 原则。针对方法返回值即消费,针对方法参数即生产。
至于原理,编译器通过 ? super T
中关键字 super 得出本次协变泛型只作用在方法参数上。
因此,你的你若调用 List<? extends T> 的 get 方法,用到了不应该使用的方法 返回值,编译器将报错。
编译器通过 ? extends T
中关键字 extends 得出本次协变泛型只作用在方法返回值上。
因此,你的你若调用 List<? extends T> 的 add 方法,用到了不应该使用的方法参数,编译器将报错。
逆变协变优点
我们用 Java 对现实世界的水果进行简单的抽象,水果抽象为 Fruit,Apple等于 Fruit 存在继承关系。盛放水果的盘子 plate 被抽象为 List。
于是我们 OOP 代码抽象得到:
class Fruit {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Watermelon extends Fruit {
}
List<Fruit> plate1 = new ArrayList<Apple>(); // 编译错误
List<Fruit> plate2 = new ArrayList<Banana>(); // 编译错误
List<Fruit> plate3 = new ArrayList<Watermelon>(); // 编译错误
编译错误:
Java 中类型存在协变关系:List != ArrayList。
但是 Java 中类型上泛型不存在协变关系,即 List<Fruit> != ArrayList<Apple>,因此编译器提示泛型协变的编译错误。
解决方法:
我们利用上面学到的 ? extends 、? super 打破泛型不变的特性,提供泛型协变,提高代码的复用性:
class A {}
class B extends A{}
class C extends A{}
// 协变,用作只读型容器————集合中元素都是 A 或 A 子类型
List<? extends A> plate1 = new ArrayList<A>();
List<? extends A> plate2 = new ArrayList<B>();
List<? extends A> plate3 = new ArrayList<C>();
// 逆变,用作只写型容器————集合中元素都是 B 或 B 父类型
List<? super B> plate4 = new ArrayList<A>();
优点
? extends、? super 为带泛型的的类型提供了协变支持,提升了代码的可复用性。
泛型协变:使得父类泛型可以引用子类泛型。
泛型逆变:使得子类泛型可以引用父类泛型。
通过上面的协变例子,
List<? extends Fruit> plate1 = new ArrayList<Apple>();
我们可以看出,List<? extends Fruit> 比直接使用 ArrayList<Apple> 更加通用。
因此我们将在 JDK 源码中,以及一些具有优良设计的第三方框架中能经常看到 ? extends、? super 的身影。
可复用性将使得框架去除臃肿显得更加精巧,以及提升可扩展性。
学习要本着大胆猜测,小心验证的原则。
super 在 JDK 中方法参数上的应用
比如 JDK8 中的消费者接口,用上了 ? super
进行方法的参数逆变:
andThen 用于链式添加 Consumer 处理参数——针对参数即生产者,因此可以用 super。
那为什么要用 super ?直接写 T 不也行吗?
通过? super T
改变 Consumer 泛型从 accept 方法只接受 T 类型变为接受 T 及 T 的父类型。
优点见下面的例子:
class A {}
class B extends A{}
public static void main(String[] args) {
new Consumer<B>() {
@Override
public void accept(B name) {
System.out.println(name);
}
}.andThen(new Consumer<A>() { // 逆变,泛型参数通用性提升
// 如果 andThen(Consumer<T> after),由于泛型不变定理那这里只能接受 B 类型
// super 逆变后 accept 除了 B 类型还 B 的父类型 A
@Override
public void accept(A s) {
System.out.println(s);
}
});
}
于是,andThen Consumer#accept 参数类型从原本只能接受 B 类型到现在可以接受 B 的父类型 A 了。
extends 在 JDK 中方法返回值上的应用
再看具有生产与消费能力的接口:Function —— 具备输入输出函数。? extends 与 ? super 消费生产协变都用上了:
用在参数上用,也有在返回值上用——即生产者与消费者,因此 extends 与 super 都用上了。
这个的优点就自己探究吧。
小结
综上,若是我们想要学习框架源码,弄明白 ? extends、? super 是不可避免的。若是想要封装自己的框架, ? extends、? super 也是要熟练使用。
通过本文的学习,可以发现对于理解 ? extends、? super 还是有一定的心智负担。对于我们普通开发来说,也为了团队代码的可读性,可以直接使用具体类型,无需一定要使用 ? extends、? super 来写代码。
简单来说,就是减少不必要的炫技。
在函数式接口中的逆变协变可能稍显复杂,但在集合中使用的情况下只需要记住 extends 消费、super 生产即可。
补充
自限定类型
class SelfBounded<T extends SelfBounded<T>> {
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
SelfBounded 类接受泛型参数 T,而 T 由一个边界限定,这个边界就是拥有 T 作为其参数的SelfBounded。
作用:保证子类对基类成员或函数参数的重写。
示例:调用set(T arg),传入的参数必定是SelfBounded的子类,而不能是SelfBounded。