0. Covariance and Contravariance, Invariant
三者的定义:
-
Covariance: accept subtypes
-
Contravariance: accept supertypes
-
Invariant:除上面2种情况
1. 数组
数组是covariant的
1.1 T[]
可以保存其子类
Number[] nums = new Number[5];
nums[0] = new Integer(1); // Ok
nums[1] = new Double(2.0); // Ok
1.2 当S<=T
时, S[]<=T[]
Integer[] intArr = new Integer[5];
Number[] numArr = intArr; // Ok
1.3 它的问题
以下的代码编译期没有问题,但是由于栈污染,在运行时会报错 ArrayStoreException
numArr[0] = 1.23; // Not ok
2. 范型
范型是invariant的,这样可以避免运行时的栈污染
由于范型在运行时由于类型擦除不知道具体类型,所以不能在运行时检测出栈污染,所以范型是invariant,运行时没有类型,没法进行子类,父类的比较
ArrayList<Integer> intArrList = new ArrayList<>();
ArrayList<Number> numArrList = intArrList; // Not ok
ArrayList<Integer> anotherIntArrList = intArrList; // Ok
3. 通配符
通配符让范型有能力支持covariance和contravariance,例如:
ArrayList<Integer> intArrList = new ArrayList<>();
ArrayList<? super Integer> numArrList = intArrList; // Ok
ArrayList<? extends Integer> inarr = intArrList; // ok
ArrayList<? super Number> numArrList = intArrList; // Not Ok
ArrayList<? extends Number> inarr = intArrList; // ok
Java中表示类型的上界和下界
下界(Contravariant):
? super Integer
上界(Covariant):
? extends Integer
4. 副作用
4.1 Covariant types是read-only
由于Covariant可以保证我们读取的数据一定是一个什么类型(可以使用它的下界)
ArrayList<? extends Number>
可以保证读出来的肯定可以转成Number
类型
但是写数据的时候不能保证,写入的是正确的类型,因为nums.add()
不能保证类型
4.2 Contravariant types是write-only
ArrayList<? super Integer>
可以保证这个对象接受Integer
对象,即我们可以写入Integer
,但不能保证我们读出来的一定是个Integer
5. 应用
Producer extends, Consumer super
producer-like的对象可以产出T
类型的对象,所以类型可以是 <? extends T>
,可以作为函数的返回参数
consumer-like的对象可以消费T
类型的对象,所以类型可以是<? super T>
,可以作为函数的入参
Ref:
- https://www.freecodecamp.org/news/understanding-java-generic-types-covariance-and-contravariance-88f4c19763d2/
- https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po/2745301#2745301
- Covariance and contravariance (computer science) - Wikipedia
网友评论