Kotlin 之泛型详解

简介: 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
    }
}


父类出现的地方,可以使用代替。类似


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


通过泛型参数拿到子类的类型,从而可以调用子类的方法


相关文章
|
5月前
|
安全 Java Kotlin
Kotlin泛型:灵活的类型参数化
Kotlin泛型:灵活的类型参数化
|
3月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
41 2
|
18天前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
29 3
|
19天前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
|
12天前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
18 0
|
16天前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
本教程详细讲解了Kotlin中的泛型概念,包括协变、逆变、类型投影及泛型函数等内容。适合已有Java泛型基础的学习者,深入理解Kotlin泛型机制。快速学习者可参考“简洁”系列教程。
27 0
|
5月前
|
安全 Java 编译器
Android面试题之Java 泛型和Kotlin泛型
**Java泛型是JDK5引入的特性,用于编译时类型检查和安全。泛型擦除会在运行时移除类型参数,用Object或边界类型替换。这导致几个限制:不能直接创建泛型实例,不能使用instanceof,泛型数组与协变冲突,以及在静态上下文中的限制。通配符如<?>用于增强灵活性,<? extends T>只读,<? super T>只写。面试题涉及泛型原理和擦除机制。
39 3
Android面试题之Java 泛型和Kotlin泛型
|
Kotlin
Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
93 0
|
安全 Java 编译器
Kotlin 泛型 VS Java 泛型
Kotlin 泛型 VS Java 泛型
78 0
|
存储 JSON 安全
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
上期主要分享了 From Java To Kotlin 1 :空安全、扩展、函数、Lambda。 这是 From Java to Kotlin 第二期。 带来 表达式思维、子类型化、类型系统、泛型。
222 0
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了