三、泛型型变:协变、逆变与不变
3.1 协变
3.1.1 基本定义
????????如果在定义的泛型类、接口和泛型方法的泛型参数前面加上 out 关键词,说明这个泛型类、接口和泛型方法是协变。
????????也就是说,A 是 B 的子类,那么 List<A> 也是?List<B> 的子类。
class Demo {
interface Producer<out T> { // 在泛型类型形参前面指定 out 修饰符
val something: T
fun produce(): T
}
} |
????????那么,协变如何解决类型不安全的问题?——只能读,不能写;只能作为方法返回值或修饰只读权限的属性,不能作为方法参数类型或可变权限的属性。
3.1.2 关键内容
- out;
- 生产者;
- 只能读,不能写;
- 对应 Java 中?Collection<? extends Object>
- 只能作为方法返回值或修饰只读权限的属性,不能作为方法参数类型;
????????注:Kotlin 中的 List 并不是 Java 中的 List,因为 Kotlin 中的 List 是个只读的 List,不具备修改集合中元素的操作方法。Java 中的 List 实际上相当于 Kotlin 中的 MutableList,它具有各种读和写的操作方法。
3.2 逆变
3.2.1 基本定义
????????如果在定义的泛型类、接口和泛型方法的泛型参数前面加上 in 关键词,说明这个泛型类、接口和泛型方法是逆变。
????????也就是说,A 是 B 的子类,那么 List<B> 反过来是?List<A> 的子类。
class Demo {
interface Consumer<in T> { // 在泛型类型形参前面指定 in 修饰符
fun consume(value: T)
}
} |
????????那么,逆变如何解决类型不安全的问题?——只能写,不能读;只能作为方法的形参类型或修饰可变权限的属性。
3.2.2 关键内容
- in;
- 消费者;
- 只能写,不能读;
- 对应 Java 中 List<? super String>
- 只能作为方法的形参类型或修饰可变权限的属性;
3.2.3 加深理解的Demo参考如下:
正常情况下:
class Demo {
private val doubleList = mutableListOf(2.0, 3.0)
private val intList = mutableListOf(2, 3)
private val doubleComparable = Comparator<Double> { d1, d2 -> // 一个 Double 类型比较器
d1.compareTo(d2)
}
private val intComparable = Comparator<Int> { i1, i2 -> // 一个 Int 类型比较器
i1.compareTo(i2)
}
fun test() {
doubleList.sortWith(doubleComparable)
intList.sortWith(intComparable)
}
} |
使用逆变后:
class Demo {
private val doubleList = mutableListOf(2.0, 3.0)
private val intList = mutableListOf(2, 3)
private val numberComparable = Comparator<Number> { n1, n2 -> // 一个 Number 父类型比较器
n1.toDouble().compareTo(n2.toDouble())
}
fun test() {
doubleList.sortWith(numberComparable)
intList.sortWith(numberComparable)
}
} |
3.3 不变
????????除去协变和逆变就是不变了,它就是我们常用的普通泛型,它既没有 in 关键字修饰,也没有 out 关键字修饰。
class Demo {
interface MutableList<E> { // 没有 in 和 out 修饰
fun add(element: E) // E可以作为函数形参类型处于逆变点,输入消费 E
fun subList(fromIndex: Int, toIndex: Int): MutableList<E> // E又可以作为函数返回值类型处于协变点,生产输出 E
}
} |
3.4 生产者、消费者的概念
????????Joshua Bloch 称那些你只能从中读取的对象为生产者,并称那些你只能写入的对象为消费者。他建议:“为了灵活性最大化,在表示生产者或消费者的输入参数上使用通配符类型”,并提出了以下助记符:
????????PECS 代表生产者-Extends、消费者-Super(Producer-Extends, Consumer-Super)。
注意:如果你使用一个生产者对象,如?List<? extends Foo>,在该对象上不允许调用?add()?或?set()。但这并不意味着该对象是不可变的:例如,没有什么阻止你调用?clear()从列表中删除所有元素,因为?clear()?根本无需任何参数。通配符(或其他类型的型变)保证的唯一的事情是类型安全。不可变性完全是另一回事。
3.5 Kotlin与Java的型变比较
| 不变 | 协变 | 逆变 |
---|
kotlin | 实现方式:<T>,可读可写 | 实现方式:<out T>,只能读不能写,生产者 | 实现方式:<in T>,只能写不能读,消费者 | Java | 实现方式:<T>,可读可写 | 实现方式:<? extends T>,只能读不能写,生产者 | 实现方式:<? super T>,只能写不能读,消费者 |
自创顺口溜:
泛型本是不变,Kotlin让它型变;
协变加逆变,就是没有裂变。
|