简介
- 参数化类型:在Java5中引入该机制,将类型由原来的具体类型参数化(类型参数),然后在使用时传入具体的类型
- 引入泛型原因:
- 之前使用的是object[]数组,当在向集合添加元素的时候,没有对元素的类型进行检查,也就是说我们向集合中添加任意对象,编译器都不会报错
- 当我们从集合中获取一个值的时候,不能都使用Object类型,需要做不进行类型检查的强制类型转换,所以容易出错
- 泛型的优点:
- 让编译器追踪参数类型,执行类型检查和类型转换
在类、接口和函数上使用泛型
泛型接口
泛型类
泛型函数
类型上界
- 类型参数T代表的都是实现了Comparable接口的类,即都实现了CompareTo方法, 如果不适用类型上界,则无法使用比较符号
协变与逆变
- Java和Kotlin都是非协变的,Java引入类型通配符来解决因无法协变导致的样板代码问题
- Java通配符形式
- 子类型上界限定符 ?extendsT 指定类型参数的上限
- 超类型下界限定符 ?superT指定类型参数的下限
- Number类型(简记为F)是Integer类型(简记为C)的父类型,我们把这种父子类型关系简记为C=>F(C继承F);而List,List代表的泛型类型信息分别简记为f(F),f©。那么我们可以这样描述协变和逆变:
- 当C =>F时,如果有f© => f(F),那么f叫做协变;
- 当C => F时,如果有f(F) => f©,那么f叫做逆变。如果上面两种关系都不成立则叫做不变。
- 注意:
- 协变和逆协变都是类型安全的
协变
- Java中数组是协变的
- 但是Java中的泛型是非协变的
- Java泛型数组使用通配符
- 问题:为什么Number的对象可以由Integer实例化,但是数组中的协变却不成立?
- 如果可以向List<? extends Number>中添加Integer,Float等,那么List<?extends Number>中将会持有各种Number子类型的对象(如Byte, Integer, Float, Double等),导致了List存储的元素类型混乱,所以为了区分内部的元素类型,Java禁止向List<? extends Number>中添加任意Number子类型的对象。但是可以添加空对象null。
逆变
PECS (Producer-Extends, Consumer-Super)
- 从对象中获取数据 读取src数据 使用 ?extends T 在Kotlin中使用 out T
- 向对象中写入数据 写入dest数据 使用 ?super T 在Kotlin中使用 in T
类型擦除
- Java和Kotlin的泛型实现,都是采用了在运行时类型擦除的方式
- 即在代码中的List,编译后在JVM中都是List,泛型附加的类型信息对JVM来说不可见
类型擦除的基本过程
- 首先,找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。
- 其次,把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。例如,Tget()就变成了Objectget(),List就变成了List。
- 最后,根据需要生成一些桥接方法。这是由于擦除了类型之后的类可能缺少某些必须的方法。这个时候就由编译器来动态生成这些方法。