前言
在实际开发中我们可能很少去写一部分代码。很多时候在源码里看到,但不太理解。需要我们熟悉源码中泛型 协变 逆变 类型投影 星投影等知识
协变 逆变的约定
如果将泛型定义一个方法的参数时,泛型在in位置。将泛型作为方法返回值,称泛型在out位置。
class BaseClass<T>{
private val data:T?=null
// 泛型在in位置
fun setData(data:T){}
// 泛型在out位置
fun getData():T?{return data}
}
只能从中读取的对象为生产者,只能写入的对象为消费者。
泛型的协变
String是Any的子类,List<String>不是List<Any>的子类。为了使List<String>为List<Any>子类,需要关键字out修饰泛型。例如List接口
public interface List<out E>:Collection<E>{}
泛型E在接口声明中用了out关键字修饰,下面代码才通过编译器验证:
fun test() {
val listAny = mutableListOf<Any>()
val listString = mutableListOf<String>()
// List<String>使List<Any>的子类
listAny.addAll(listString)
}
将泛型E作为方法的返回值,该参数类型是只读的。如果强行的将泛型作为方法的参数,Kotlin则提示语法错误
class BaseClass<out T>{// T类型只读
private val data:T?=null
// 泛型在in位置,编译报错,因为T类型只读,不能写入
fun setData(data:T){}
// 泛型在out位置
fun getData():T?{return data}
}
泛型类或接口中,如果A是B的子类,同时Class<A>又是Class<B>的子类,我们称类Class在该泛型上是协变的。A-->B class<A> --协变-> Class<B>。协变的目的为防止类型转换的隐患
fun test() {
val baseClass = BaseClass<Student>()
val student = Student()
baseClass.setData(student)
// 编译不通过,把baseClass给了方法syncData,但是内部baseClass通过setData传了teacher
syncData(baseClass)
}
private fun syncData(baseClass:BaseClass<Person>){
val teacher = Teacher()
baseClass.setData(teacher)
}
open class Person
class Student:Person()
class Teacher:Person()
class BaseClass<T>{
private val data:T?=null
fun setData(data:T){}
fun getData():T?{return data}
}
所以生命力泛型是协变的,该泛型就应该只读,只能放在out位置。特殊情况,我们可以将一个协变放到in位置。例如我们只用这个反射进行类型上的判断或属性读取,不涉及类型的转换。例如List的接口contains()
public interface List<out E>:Collection<E>{
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}
强行声明一个out泛型在in位置,需要使用@UnsafeVariance注解来标记。告诉编译器确定这样用,带来类型转换的风险也需要自己承担。
泛型的逆变
如果类A是类B的子类,而Class<B>又是Class<A>的子类,我们称该类是逆变的。
某个类在某泛型上逆变用关键字in。
fun test() {
val transform = object :Transform<Person>{
override fun transform(t: Person): Int {
return t.hashCode()
}
}
// 编译异常
transformAction(transform)
}
interface Transform<T>{
fun transform(t:T):Int
}
open class Person
class Student:Person()
fun transformAction(transform:Transform<Student>){
val student = Student()
transform.transform(student)
}
用关键字in处理上面异常
fun test() {
val transform = object :Transform<Person>{
override fun transform(t: Person): Int {
return t.hashCode()
}
}
// 编译通过
transformAction(transform)
}
interface Transform<in T>{
fun transform(t:T):Int
}
open class Person
class Student:Person()
fun transformAction(transform:Transform<Student>){
val student = Student()
transform.transform(student)
}
将泛型T放在in位置(方法参数),不会放在out位置(方法返回值)。泛型T放到out位置的隐患:
fun test() {
val transform = object :Transform<Person>{
override fun transform(t: Person): Person {
return Teacher()
}
}
transformAction(transform)
}
interface Transform<in T>{
fun transform(t:T):@UnsafeVariance T
}
open class Person
class Student:Person()
class Teacher:Person()
fun transformAction(transform:Transform<Student>){
val student = Student()
transform.transform(student)
}
和协变一样,可以将注解in关键字修饰的泛型放到out位置,带来的风险需要自己承担。in位置只能作为方法的参数来使用,out位置只能作为方法的返回值来使用。如果强行使用注解逃避编译器的检查,类型转换的风险就需要自己承担。逆变在Kotlin内部API的Comparable示例:
fun test() {
val student = Person(20)
compare(student)
}
// 逆变
public interface Comparable<in T>{
public operator fun compareTo(other: T):Int
}
open class Person(private val age:Int):Comparable<Person>{
override fun compareTo(other: Person): Int {
return if (this.age==other.age)0
else if (this.age>other.age)1
else -1
}
}
class Student(private val age:Int):Person(age)
fun compare(student :Comparable<Student>){
val jack = Student(18)
val result = student.compareTo(jack)
println(result)
}
使用逆变让Comparable<Person>变成了Comparable<Student>的子类。
类型投影 -> 使用处形变
在声明泛型类的时候,该泛型类在该泛型上既不是逆变也不是协变。Kotlin中的Array
class Array<T>(val size:Int){
fun get(index:Int):T{}
fun set(index:Int,value:T){}
}
开发中不太灵活,实际开发中定义copy
fun test() {
val arrayFrom = arrayOf(1,2,3)
val arrayTo = Array<Any>(3){}
// 编译报错
copy(arrayFrom,arrayTo)
}
fun copy(from:Array<Any>,to:Array<Any>){
assert(from.size==to.size)
for (i in from.indices){
to[i] =from[i]
}
}
Int是Any的子类。但是Array<Int>并不是Array<Any>的子类,编译器不允许这么操作。类型投影可以使Array<Int>成为Array<Any>子类型,这样的使用场景称类型投影:
fun test() {
val arrayFrom = arrayOf(1,2,3)
val arrayTo = Array<Any>(3){}
// 编译报错
copy(arrayFrom,arrayTo)
}
// 使用out投影一个类型
fun copy(from:Array<out Any>,to:Array<Any>){
assert(from.size==to.size)
for (i in from.indices){
to[i] =from[i]
}
}
使用out投影一个类型。该类使用处可以调get()方法,而不能调用set()方法。因为copy()函数中给from数组存放一个String,随时造成类型转换异常。int是Any的子类,String也是Any子类。当我们使用out投影Array,那么Array使用将受限,同时也规避了类型转换的风险。同样可以使用in来投影一个类型
fun test() {
val arrayFill = Array<Any>(3){}
fill(arrayFill,"str")
}
fun fill(dest:Array<in String>,value:String){
dest[0] = value
val str = dest[0]
println(str)
}
让Array<Any>成了Any<String>的子类,将Array<Any>传递给fill()函数。在fill()函数中,只能设置String类型给到dest数组,取出来的也是String,虽然将Array<Any>传递给了fill()数组,使用处,只能存放String数组
星投影->子类型的投影
interface Factory<out T:Person>{
fun create():T
}
接口Factory中,T是一个拥有上界的Person协变类型参数,T未知,可以安全的从中读取Person的值
interface Factory<in T:Person>{
fun setInfo(info:T)
}
T是一个拥有上界Person逆变类型参数,T未知,可以安全方式写入Factory<in T:Person>。
总结
泛型知识相对比较难理解,掌握泛型基础知识,就能解决比较复杂的场景。