Kotlin 之泛型详解
泛型约束
fun <T : Comparable<T>> maxOf(a: T, b: T): T {
if (a > b) {
return a
}
return b
}
fun <T> callMax(a: T, b: T) where T : Comparable<T>, T : () -> Unit {
a()
b()
}
泛型的型变
泛型型变分为三种
-
不变
没有继承关系,传什么就是什么,只有一个类型
-
协变: out
- 只读不写
- 父类出现的地方可以用子类代替
只读不写:
val a: Array<out Number> = Array<Int>(2, { 3 }) a[0] //可读 a[0] = 2 //ERROR
open class W<out K> { //Error 不可写 fun add(k: K) { } //可读 fun get(): K? { return null } }
父类出现的地方,可以使用代替。类似<? extends T>
fun main() { val b = Box<Number>() b.a(Array<Int>(2, { 5 }))//error } class Box<E> { fun a(value: Array<E>) { } }
因为 E 是 Number,所以传入 Int 肯定是错误的。修改如下:
fun a(value: Array<out E>) {}
out 代表协变,表示继承自 E 的都可以。这个时候传入 Int 就没问题了。因为 Int 是继承 Number 的。
注意:
class Box<E> { fun a(value: Array<out E>) { value[0] =1 //Error } }
-
逆变:in
-
只写不读。某些情况可以读,但是只能往外取放在 Object 中
-
子类出现的地方,可以使用父类代替
只写不读
val a: Array<in Int> = Array<Number>(2, { 3 }) val s = a[0] // 不能确定返回值的类型,只能使用Any类型接收 a[0] = 2
open class W<in K> { //可写 fun add(k: K) { } //返回值 K 报错,不可读 fun get(): K? { return null } }
子类出现的地方,可以使用父类代替
fun main() { val b = Box<Int>() b.b(Array<Number>(2, { 5 }))//error } class Box<E> { fun b(value: Array<E>) { } }
E 是 Int,传入的是 Number。所以报错,修改如下
fun b(value: Array<in E>) {}
in 代表逆变,表示只要是 E 的父类都可以。所以传入 Number 就没问题了。Number 是 int 的父类
注意:
fun b(value: Array<in E>) { val s = value[0] //不能确定返回值的类型,只能使用Any类型接收 }
-
UnsafeVariance
违法形变约束
- 即声明为协变的类出现逆变,或者相反
- 声明为不变的类接收协变或者逆变类型的参数
class Dustbin<in T> {
//报错,mutableListOf 本身是协变,但是传入的是逆变,所以报错。
val list = mutableListOf<T>()
fun put(t: T) {
list += t
}
}
修改如下:
val list = mutableListOf<@kotlin.UnsafeVariance T>()//使用注解,忽略形变即可
星投影
-
‘*’ 可以用在变量类型的声明位置
-
‘*’ 可以描述一个未知的类型
-
‘*’ 所替换的类型在:
- 协变点返回泛型参数上限类型
- 逆变点接受泛型参数下限类型
-
不能直接或间接使用在属性或者函数上
-
对于 Foo ,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo <> 等价于 Foo 。 这意味着当 T 未知时,你可以安全地从 Foo <> 读取 TUpper 的值。
-
对于 Foo ,其中 T 是一个逆变类型参数,Foo <> 等价于 Foo 。 这意味着当 T 未知时,没有什么可以以安全的方式写入 Foo 。
-
对于 Foo ,其中 T 是一个具有上界 TUpper 的不型变类型参数,Foo<*> 对于读取值时等价于 Foo 而对于写值时等价于 Foo。
out:当接收可协变的泛型参数(out T )时, * 映射的类型为 Any?
open class W<out K> {
//Error
/* fun add(k: K) {
}*/
fun get(): K? {
return null
}
}
fun main() {
val w = W<Number>()
val w1: W<*> = W<Number>()
val s = w1.get() //返回类型为 Any?
}
下限:当接收可逆变泛型参数(in T) 时,* 映射类型为 Nothing。
注意:因为下限无法定义,所以所有的下限类型都为 Nothing
fun main() {
val w1: W<*> = W<Number>()
w1.add("Nothing")//报错,只支持 Nothing
}
open class W<in K> {
fun add(k: K) {
}
}
泛型实现原理
伪泛型:如果使用了泛型,则会在编译时擦除类型,运行时无实际类型生成。这点和 Java一样
真泛型:C#
泛型类型无法当做真实类型
fun <T> genericMethod(t: T) {
val t = T() //Error
val ts = Array<T>(3){ TODO() } //Error ,Array 在编译的时候类型时确定的,不会擦除
val cls = T::class.java //Error 无法获取 class
val list = ArrayList<T>() //可以
}
内联特化
如果一个函数是内联函数,那么他会在调用处执行。一旦在调用的地方执行内联函数体,我们就可以知道他的类型时确定的了。
声明如下:
inline fun <reified T> genericMethod(t: T) {
val t = T() //Error
val ts = Array<T>(3){ TODO() }
val cls = T::class.java
val list = ArrayList<T>()
}
例子:
fun <T> fromJson(json: String, classOft: Class<T>): T {
}
fun <T> test(json: String) {
fromJson(json, T::class.java)//Error
}
在调用 fromJson 的时候,无法传入 T 的class。因为 T 在编译的时候会被擦除。
这个时候就可以曲线救国:使用 内联特化:
inline fun <reified T> test(json: String) {
fromJson(json, T::class.java)//Success
}
这个函数中的代码会被内联到调用处,所以泛型的内心自然也就晓得了。
- 泛型原理
- 泛型实现
- 伪类型擦除:在编译时会擦除为上限类型
- 真:类型生成
- 内联特化
- 扩充伪泛型的限制
- 泛型实现
案例:
一般情况下,在继承关系中,子类可以调用父类的方法,但是父类无法调用子类方法。但是通过接口和泛型可以实现调用子类方法:
typealias OnConfirm = () -> Unit
typealias OnCancel = () -> Unit
private val EmptyFunction = {}
open class Notification(
val title: String,
val content: String
)
class ConfirmNotification(
title: String,
content: String,
val onConfirm: OnConfirm,
val conCancel: OnCancel
) : Notification(title, content)
/**
* 实现 SelfType 接口。泛型是 NotificationBuild 的子类
* 注意 SelfType 有一个属性 self。是泛型的类型
* 所以 可以通过返回的 self 调用子类的方法。
*/
open class NotificationBuild<Self : NotificationBuild<Self>> : SelfType<Self> {
protected var title = ""
protected var content = ""
fun title(title: String): Self {
this.title = title
return self
}
fun content(content: String): Self {
this.content = content
return self
}
open fun build() = Notification(title, content)
}
//将子类类型作为泛型传给父类
class ConfirmNotificationBuilder : NotificationBuild<ConfirmNotificationBuilder>() {
protected var onConfirm: OnConfirm = EmptyFunction
protected var onCancel: OnCancel = EmptyFunction
fun onConfirm(onConfirm: OnConfirm): ConfirmNotificationBuilder {
this.onConfirm = onConfirm
return this
}
fun onCancel(onCancel: OnCancel): ConfirmNotificationBuilder {
this.onCancel = onCancel
return this
}
override fun build(): ConfirmNotification {
return ConfirmNotification(title, content, onConfirm, onConfirm)
}
}
interface SelfType<Self> {
//将 this 强转为 泛型的类型
val self: Self
get() = this as Self
}
fun main() {
//创建 ConfirmNotificationBuilder 对象时,就将当前类作为泛型传给父类了
ConfirmNotificationBuilder()
.title("标题")
.content("内容")
.onCancel {
println("OnCancel")
}
.onConfirm {
println("OnConfirm")
}
.build()
.onConfirm()
}
-
泛型
-
基本概念:1,泛型参数,2,函数,类添加泛型
-
泛型约束:
- 泛型上限:通过 where 语句添加多个上限
-
泛型形变
- 不变:指定的泛型类型
- 协变:只读不写,意思是只能读取,不能写入
- 逆变:只写不读,某些情况可以读,但是只能是 Any 类型
-
泛型形变点
- 协变点:函数返回值类型为泛型参数类型
- 逆变点:函数参数为泛型参数类型
-
UnsafeVariance
- 型变点与泛型不一致是使用这个注解
- UnsafeVariance
-
星投影
- 协变向上,获取上限
- 逆变向下,获取下限,所以类型的下限为 Nothing,不可被添加。
-
泛型原理和内联特化
- 泛型的擦除,在编译时,泛型的类型会被擦除。
- 调用某一个泛型方法,无法直接使用泛型,这时就可以使用内联特化。该方法会在调用处执行。泛型是明确的。
-
模拟Self Type
- 通过泛型参数拿到子类的类型,从而可以调用子类的方法
-
上一篇: 关于Java泛型擦除的一些理解