参考文献:https://www.jianshu.com/p/2bf15c5265c5
https://www.jianshu.com/p/da1127c51c90
今天刚开始看kotlin的泛型语法和概念,觉得之前java中学过泛型,可能这个也差不多吧。。。。。嗯,确实差不多,想着跟之前一样用类比java的方式继续理解kotlin泛型,结果看了两篇java的泛型之后。。。。。。发现java泛型之前没怎么学懂
之前在学java泛型时候没有接触到的两个概念:协变和逆变。下面提到的可能大家都知道,只是我已自己的理解将协变和逆变的概念表述出来:
逆变与协变用来描述类型转换(type transformation)后的继承关系:A、B表示类型,f(·)表示类型转换,A<=B表示A为B的子类,那么则存在:
看的有点懵逼?先别着急,等会儿回过头来再看这个。。这里介绍了协变和逆变的概念,对于java中数组是协变的,如下所示:
public static void main(String[] args) {
String[] strings = new String[5];
Object[] objects = strings;
}
实例中创建了一个字符串数组对象,但是用Object数组同样可以引用。
实例中String类<=Object类,对应的String[]<=Object[],所以可以得出数组是协变类型。
现在问题来了:
现在将string类型的数组引用赋值给了object类型的数组引用,在操作时候是不是可以赋值除了string意外的类型呢?
public static void main(String[] args) {
String[] strings = new String[5];
Object[] objects = strings;
try {
objects[0] = 1;
System.out.println(strings[0]);
} catch (Exception e) {
System.out.println("出错了吧。。。。。");
e.printStackTrace();
}
}
给objects第一个元素设置一个int类型的1,结果如下:
java.lang.ArrayStoreException: java.lang.Integer
出错了吧。。。。。
at as.a.Str.main(Str.java:12)
看来数组以协变方式允许类型向上转型,但是会有写入安全的问题,如上异常
现在我们看下在集合中使用会是怎么样的:
public static void main(String[] args) {
String[] strings = new String[5];
Object[] objects = strings;
try {
objects[0] = 1;
System.out.println(strings[0]);
} catch (Exception e) {
System.out.println("出错了吧。。。。。");
e.printStackTrace();
}
List strList = new ArrayList<>();
List
在将strList赋值给objList时候,已经出现编译错误了,错误结果如下:
使用泛型时,在编译期间存在泛型擦除过程,取消了运行时检查,所以将泛型的错误检查提前到了编译器。并不是因为是两个不同的类型。
这时候用到了泛型通配符? extends T 和 ? super T了。。首先示例的继承关系如下:
class 生物 {
}
class 动物 extends 生物 {
}
class 人 extends 动物 {
}
class 狗 extends 动物 {
}
class 山顶洞人 extends 人 {
}
class 半坡人 extends 人 {
}
示例如下:
public class Str {
public static void main(String[] args) {
List extends 动物> objList = new ArrayList<人>();
动物 动物 = objList.get(0);//编译通过
生物 动物1 = objList.get(0);//编译通过
人 人 = objList.get(0);//编译错误
objList.add(new 动物());//编译错误
objList.add(new 人());//编译错误
objList.add(new 狗());//编译错误
}
}
示例中将动物和生物类型引用objList的元素时,编译无错误,但是将人类型引用objList元素时,编译出错了。然后,,,,,,不管什么类型,只要add就全都编译错误了。
我是这样想的,如果说他允许add T及其子类对象,那他是如何知道哪些类型的对象是应该添加的呢?举个简单的?,List extends 动物> 存放都是动物的子类,但是无法确定是哪一个子类,这种情况下依然会出现安全问题(如上栗中String数组中+int);而接收引用也同样是这个道理:我存放的是你T的子类,但是无不知道啊,那我接收的引用只要是你的父类就好啦。
这里简单总结一下上限通配符? extends T 的用法,? extends T表示所存储类型都是T及其子类,但是获取元素所使用的引用类型只能是T或者其父类。使用上限通配符实现向上转型,但是会失去存储对象的能力。上限通配符为集合的协变表示
想要存储对象,就需要下限通配符 ?super T 了,用法如下:
public static void main(String[] args) {
List super 人> humList = new ArrayList<>();
humList.add(new 半坡人());//编译通过
humList.add(new 山顶洞人());//编译通过
humList.add(new 人());//编译通过
humList.add(new 动物());//编译失败
}
相信大家一眼就看出来了,添加人及其子类没有错误,一旦再网上就出现编译错误了。
下限通配符 ? super T表示 所存储类型为T及其父类,但是添加的元素类型只能为T及其子类,而获取元素所使用的类型只能是Object,因为Object为所有类的基类。下限通配符为集合的逆变表示。
现在反过头来看一下最开始说的协变和逆变的概念:
以下是笔者对以上内容的总结四句话:
?extends T 存放的类型一定为T及其子类,但是获取要用T或者其父类引用。转型一致性
?super T 存放的类型一定为T的父类,但添加一定为T和其子类对象。转型一致性
?extends T 进行add(T子类)编译出错:因为无法确定到底是哪个子类
?super T get()对象,都是Object类型,因为T的最上层父类是Object,想要向下转型只能强转。
对于泛型还有生产者消费者的概念,笔者打算放在下一篇和kotlin的泛型卸写在一起。