欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin 之泛型详解

程序员文章站 2024-03-14 18:36:23
...

泛型约束

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

      • 通过泛型参数拿到子类的类型,从而可以调用子类的方法
相关标签: Kotlin