简介

  • 参数化类型:在Java5中引入该机制,将类型由原来的具体类型参数化(类型参数),然后在使用时传入具体的类型
  • 引入泛型原因:
  • 之前使用的是object[]数组,当在向集合添加元素的时候,没有对元素的类型进行检查,也就是说我们向集合中添加任意对象,编译器都不会报错
  • 当我们从集合中获取一个值的时候,不能都使用Object类型,需要做不进行类型检查的强制类型转换,所以容易出错
  • 泛型的优点:
  • 让编译器追踪参数类型,执行类型检查和类型转换

在类、接口和函数上使用泛型

泛型接口
interface Factory<T> {
fun produce(production : T) : T
}


@Test
fun testInterface() {
val production = object : Factory<String> {
override fun produce(production: String): String {
println(production)
return "$production is producing"
}
}
println(production.produce("Chocolate"))
}
泛型类
class Container<K, V> (var key : K, var value : V) {
override fun toString(): String {
return "Container(key = $key, value = $value)"
}
}
@Test
fun testContainer() {
val container = Container<Int, String>(1, "wjx")
println(container.toString()) // Container(key = 1, value = wjx)
}
泛型函数
fun <T> console(t : T) {
println(t)
}
console(1) // 1
console("wjx") // wjx
类型上界
fun <T : Comparable<T>> gt(x : T, y : T) : Boolean // T的类型上界是Comparable<T>
  • 类型参数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中数组是协变的
Integer[] ints = new Integer[3];
ints[0] = 0;
ints[1] = 1;
ints[2] = 2;
Number[] numbers = new Number[3];
numbers = ints; //数组是协变的,可以正确赋值
for (Number n : numbers) {
System.out.println(n);
}
  • 但是Java中的泛型是非协变的
class A{}
class B extends A{}
public static void main(String[] args) {
List<B> bList = new ArrayList<>();
bList.add(new B());
List<A> aList = (List<A>) bList; // 编译错误,类型不兼容
}
  • Java泛型数组使用通配符
List<? extends Number> list = new ArrayList<Number>();
list.add(new Integer(1)); //error
  • 问题:为什么Number的对象可以由Integer实例化,但是数组中的协变却不成立?
List<? extends Number> list= new ArrayList<>();
  • 如果可以向List<? extends Number>中添加Integer,Float等,那么List<?extends Number>中将会持有各种Number子类型的对象(如Byte, Integer, Float, Double等),导致了List存储的元素类型混乱,所以为了区分内部的元素类型,Java禁止向List<? extends Number>中添加任意Number子类型的对象。但是可以添加空对象null。

逆变

List<? super Number> list3 =new ArrayList<Number>() ;//Java 中的<? super Number >通配符
List<? super Number> list4 = new ArrayList<Object>() ;
list3.add(new Integer(3)) ; //可以添加 Integer 类型的元素
list4.add(new Integer(4));

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。
  • 最后,根据需要生成一些桥接方法。这是由于擦除了类型之后的类可能缺少某些必须的方法。这个时候就由编译器来动态生成这些方法。