逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;
f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
//让LongList赋值给NumberLsit
ArrayList<? extends Number> l=new ArrayList<Long>();
//让NumberList赋值给LongList
ArrayList<? super Long> ll=new ArrayList<Number>();
1.泛型的不变性
下面看个例子:
public class Tmp {
public static void main(String[] args) {
Number number;
Long l=1L;
number=l; //Long是Number的子类
ArrayList<Number> numberArrayList;
ArrayList<Long> longArrayList=new ArrayList<>();
numberArrayList=longArrayList; //编译报错
}
}
为什么不能将longArrayList赋给numberArrayList呢?
我们想象下如果可以,我再在numberArrayList中加入一个Short对象,如下:
public class Tmp {
public static void main(String[] args) {
Number number;
Long l=1L;
number=l; //Long是Number的子类
ArrayList<Number> numberArrayList;
ArrayList<Long> longArrayList=new ArrayList<>();
numberArrayList=longArrayList; //编译报错
numberArrayList.add(new Short("1"));
numberArrayList.get(0);
}
}
那么这里numberArrayList.get(0);到底取出来应该是什么类型的呢?
为了避免这种情况,Java把泛型定义为不可变的,即ArrayList<Long>和ArrayList<Number>并不具有Long和Number那样的关系。这是因为泛型是在编译期
检测类型合法性的,而在实际运行期泛型
类型被擦除
了,所以在泛型看来ArrayList<Long>和ArrayList<Number>是两种不同的类型。
2.泛型的协变性(上界)
有时我们就是想让longArrayList赋给numberArrayList怎么办呢?
public class Tmp {
public static void main(String[] args) {
ArrayList<Long> longArrayList=new ArrayList<>();
long l=sum(longArrayList);
}
/**
* ? extends 使泛型可以协变
* @param list 可接收Number及其子类List
* @return
*/
private static long sum(List<? extends Number> list){
long res=0;
for (Number n:list) {
res+=n.longValue();
}
return res;
}
}
协变可以让泛型的约束变得宽松,但也有相应的代价。
协变<? extend T> 不能存,只能取。即,
不能调用<? extend T>泛型类以T为形参的方法,只能调用以T为返回值的方法。
例如,我们修改上面的代码,
public class Tmp {
public static void main(String[] args) {
ArrayList<Long> longArrayList=new ArrayList<>();
long l=sum(longArrayList);
}
/**
* ? extends 使泛型可以协变
* @param list 参数可接收Number及其子类List
* @return
*/
private static long sum(List<? extends Number> list){
list.add(1.1F);//不能往里存
list.add(1);//不能往里存
long res=0;
for (Number n:list) {
res+=n.longValue();
}
return res;
}
}
不能存,只能取是因为,这里list没有一个共同的子类,却有一个共同的父类Number,往外取时类型为Number或其父类总是没问题的。
JVM 在设计初期就没有考虑过泛型,因此对于 JVM 编译成的字节码来说,也没有泛型的概念,JVM 会使用一个占位符 CAP#1 来表示 p
接受一个 Number或子类,这里就通过 CAP#1 把类型擦除了。所以无论想往 p
插入任何类型都不可以(因为你不能赋值一个 CAP#1 类型)。但是你可以从 p
中往外取 CAP#1,因为 CAP#1 代表的是 Number 及其子类,因此往外取时,类型为 Number及其超类就总是安全的。
3.泛型的逆变(下界)
<? super Number> 只能存,不能取
public class Tmp {
public static void main(String[] args) {
ArrayList<Object> arrayList=new ArrayList<>();
sum(arrayList);
}
/**
* ? extends 使泛型可以协变
* @param list 参数可接收Number及其子类List
* @return
*/
private static void sum(List<? super Number> list){
list.add(1.1F);//能往里存
list.add(1);//能往里存
for (Object n:list) {
System.out.println(n);
}
}
}
List<? super Number> list中下界是Number,那往里存粒度比Number小的都是安全的。但往外读取元素就费劲了,只有所有类的父类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。
<? super Long> 下界为Long,代表里面放着Long及其父类, JVM 会使用一个占位符 CAP#1 来表示 p 接受一个Long或其父类, 这里就通过 CAP#1 把类型擦除了。 所以无论想从 p 拿出任何类型都不可以(因为你不不知道CAP#1是个什么类型)。 但是你可以往 p 插入CAP#1,因为 CAP#1 代表的是Long及其父类, 因此插入Long及Long的子类总是安全的
总结一下
public class Tmp {
public static void main(String[] args) {
//让LongList赋值给NumberLsit
/*
<? extends Number>
代表里面放着Number及其子类,
JVM 在设计初期就没有考虑过泛型,因此对于 JVM 编译成的字节码来说,也没有泛型的概念,
JVM 会使用一个占位符 CAP#1 来表示 p 接受一个 Number或子类,
这里就通过 CAP#1 把类型擦除了。
所以无论想往 p 插入任何类型都不可以(因为你不能赋值一个 CAP#1 类型)。
但是你可以从 p 中往外取 CAP#1,因为 CAP#1 代表的是 Number 及其子类,
因此往外取时,类型为 Number及其超类就总是安全的。
*/
ArrayList<? extends Number> l = new ArrayList<Long>();
//让NumberList赋值给LongList
/*
<? super Long>
代表里面放着Long及其父类,
JVM 会使用一个占位符 CAP#1 来表示 p 接受一个Long或其父类,
这里就通过 CAP#1 把类型擦除了。
所以无论想从 p 拿出任何类型都不可以(因为你不不知道CAP#1是个什么类型)。
但是你可以往 p 插入CAP#1,因为 CAP#1 代表的是Long及其父类,
因此插入Long及Long的子类总是安全的
*/
ArrayList<? super Long> ll = new ArrayList<Number>();
}
}
4. PECS(Producer Extends Consumer Super)
Producer Extends 你写的类是主要作为生产者向外提供数据,那么就用 extends
Consumer Super 你写的类是主要作为消费者,需要数据,那么就用 super