目录
泛型与逆变、协变、不变
假设有Parent、Son、GrandSon三个类
class Parent{}
class Son extends Parent{}
class GrandSon extends Son{}
1. 数组是协变的
由于Son可以向上转型为Parent , 所以Son[] 可以向上转型成Parent[] ,形如这种A>B,则f(A) > f(B),就叫协变,我们都变了,但是关系不会变
会发现编译器并不会报错
由于 Parent parent = (Parent)new Son();
所以 Parent[] arr = (Parent[]) new Son[5];
虽然一旦运行起来,arr[2] = new Parent() 会抛出ArrayStoreException异常
但以下三个在编译期间均不会报错
arr[0] = new GrandSon();
arr[1] = new Son();
arr[2] = new Parent();
2. 未使用通配符的泛型类是不变的(为何泛型类无法强转)
由于Son可以向上转型为Parent,但是List<Parent> parents = (List<Parent>)new ArrayList<Son> 直接编译期报错,同时List<Son> sons = (List<Son>)new ArrayList<Parent>同样直接编译期报错,这种A>B,但f(A)和f(B)之间完全没有任何关系,就叫不变
Parent obj = (Parent)new Son();
但以下两个编译期直接报错
List<Parent> parents = (List<Parent>)new ArrayList<Son>();
List<Son> sons = (List<Son>)new ArrayList<Parent>();
所以泛型类无法强转
3. 使用通配符的泛型类可逆变可协变(泛型类强转解决方案)
由于Son可以向上转型为Parent,且 List<? extends Parent> list = (List<? extends Parent>)new ArrayList<Son>() ,符合A>B,则f(A) > f(B),所以通配符泛型可以是协变的。
但是所有add操作都会报错,因为? extends Parent表示泛型可以是Parent的任何子类,一个父类可以有无限个子类,所以List无法确定究竟该接收哪个,所以选择全报错。
Parent obj = (Parent)new Son();
发现这样写并不报错
List<? extends Parent> list = (List<? extends Parent>)new ArrayList<Son>();
但是所有添加行为都报错
list.add(new Son());
list.add(new GrandSon());
list.add(new Parent());
List<?> list = new ArrayList<>();
同样所有添加行为都报错
list.add(new Son());
list.add(new GrandSon());
list.add(new Parent());
由于Son可以向上转型为Parent,且List<? super Son> superList = (List<? super Son>)new ArrayList<Parent>(),符合A>B,则f(B) > f(A),所以通配符泛型可以是逆变的。只要add(Son以及Son的子类)就不会报错,因为一个类只有有限的父类,可以清晰的知道可以传入哪些子类,所以不会报错。
List<? super Son> superList =(List<? super Son>) new ArrayList<Parent>();
superList.add(new Parent()); // 编译报错
superList.add(new Son()); // 不报错
superList.add(new GrandSon()); // 不报错
所以
1. 如果想要强转,需要通过通配符
2. 如果强转的过程中,需要对传入泛型对象进行修改,需要遵循psce原则(有类似add、set等操作,需要使用List<? super Son>;有类似于get的操作,使用List<?extend Son>)
public static void main(String[] args) {
List<Son> sons = new ArrayList<>();
List<Parent> parents = forceCast(sons);
}
private static <T> List<Parent> forceCast(List<?> list) {
return (List<Parent>)list;
}
psce原则:
1. 生产者,即set或add,需要用super
2. 消费者,即get,需要用extend
3. 该方法表明,从Son或Son子类列表中取,加到Parent列表中
private static <T> void transfer(List<? super Son> dest, List<? extend Son> from) {
Son son = from.get(0);
dest.add(son)
}