一、定义
若类A是类B的子类,则记作A ≦ B。设有变换f(),若:
- 当A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性。
- 当A ≦ B时,有f(B)≦ f(A),则称变换f()具有逆变性。
- 当A ≦ B时,f(A)与f(B)无关,则称变换f()具有不变性。
二、数组协变,泛型不变
1. 数组的协变性
设有Super和Sub两个类,且Sub继承自Super
public class Super{}
class Sub extends Super{}
则如下代码在Java中是允许的:
Super[] sups = new Sub[];
这里的f()就是从类延伸到数组的变换,而原有的继承关系不变,所以说Java的数组是协变的。
看似合理的语言设计,其实是存在一些漏洞的。考虑下面的代码:
Object[] objs = new Integer[10];
objs[0] = "afly";
很不合理吧?但是上面的代码在编译时没有报错,只会在运行期抛出ArrayStoreException。这就是数组协变带来的静态类型漏洞:编译期无法完全保证类型安全。看上去Java的设计者是在程序的易用性与类型安全之间做了取舍,因为如果不支持数组协变,一些通用的方法如Arrays.sort(Object[])确实没办法正常工作。
2. 泛型的不变性
依然以Super和Sub为例。下面两行代码在Java中是不允许的:
List<Super> supList = new LinkedList<Sub>(); //error
List<Sub> subList = new LinkedList<Super>(); //error
可以看出,Java的泛型具有不变性。但是泛型的不变性也会带来使用上的不灵活,为此,Java使用有界类型使得泛型可以支持协变与逆变:
List<? extends Super> list = new ArrayList<Sub>(); //允许,协变性
List<? super Sub> list = new ArrayList<Super>(); //允许,逆变性
3. 泛型有界的副作用
class A {}
class B extends A{}
class C extends B{}
<? extends T>
<? extends T> a, a这个变量可以接受 T 及其 T 子类的集合,上界为 T,并且从 a 取出来的类型都会被强制转换为 T
List<A> as = new ArrayList<>();
List<B> bs = new ArrayList<>();
List<C> cs = new ArrayList<>();
// 可以通过编译
List<? extends B> b1 = cs;
// 不能通过编译,只能接受B及其子类的集合
List<? extends B> b2 = as;
// 重点注意:下面三行都不能通过编译
b1.add(new A());
b1.add(new B());
b1.add(new C());
// 重点注意:可以通过编译
b1.add(null);
<? extends T>最需要注意的是,就是不能向里面添加除null之外的其他所有元素
<? super T>
<? super T>,它和 <? extends T> 有点相反。对于 <? super T> a,a 这个变量可以接受 T 及其 T 父类的集合,下界为 T,并且从 a 取出来的类型都会被强制转换为 Object
List<A> as = new ArrayList<>();
List<B> bs = new ArrayList<>();
List<C> cs = new ArrayList<>();
// 可以通过编译
List<? super B> b1 = as;
// 不能通过编译,只能接受B及其父类的集合
List<? super B> b2 = cs;
// 重点注意:不能通过编译,只能添加 B 及其 B 的子类
b1.add(new A());
// 重点注意,可以通过编译
b1.add(new B());
b1.add(new C());
b1.add(null);
注意,<? super T>最需要注意的是,在虽然可以接受 T 及其父类的赋值,但是只能向里面添加 T 及其 T 的子类。
总结
-
List<? extends T> a ,可以把 a 及其 a 的子类赋给 a,从 a 里取的元素都会被强制转换为 T 类型,不过需要注意的是,不能向 a 添加任何除 null 外是元素。
-
List<? super T> a ,可以把 a 及其 a 的父类赋给 a,从 a 里取的元素都会被强制转换为 Object 类型,不过需要注意的是,可以向 a 添加元素,但添加的只能是 T 及其子类元素。
3. PECS原则
PECS(Producer-Extends Consumer-Super)
- 频繁往外读取内容的,适合用上界Extends
- 经常往里插入的,适合用下界Super