Kotlin系列五:泛型及其高级特性

目录

一 泛型

1.1 泛型类

1.2 泛型方法

 1.3 限制泛型类型

二 类委托和委托属性

2.1 类委托

2.2 委托属性

三 泛型的高级特性

3.1 泛型实化

3.2 泛型的协变

3.3 泛型的逆变


一 泛型

1.1 泛型类

class MyClass <T>{
    fun method(param :T): T {
        return param
    }
}
val myClass =MyClass<Int> ()
val result = myClass.method(123)

1.2 泛型方法

class MyClass {
    fun <T> method(param :T): T {
        return param
    }
val myClass =MyClass ()
val result = myClass.method<Int>(123)

//Kotlin出色的类型推导机制
val myClass =MyClass ()
val result = myClass.method(123)

实践举例:完成一个类似apply扩展函数的build功能

fun <T> T.build(block:T.() ->Unit):T{
        block
        return this
    }

 1.3 限制泛型类型

举例:将泛型上界设置为Number 类型(将method方法的泛型指定成数字类型)

class MyClass {
    fun <T :Number> method(param :T): T {
        return param
    }
}

默认情况下,所有泛型都是可以指定成可空类型的,这是因为在不动手指定上界的时候,泛型的默认上界是Any?;如果想让泛型的类型不可为空,只需将上界指定成Any 。


二 类委托和委托属性

委托是一种设计模式 ,它的基本理念是 :操作对象自己不会去处理某段逻辑,而是把工作委托给另外一个辅助对象去处理。

好处: 如果一个类,让大部分的方法实现都是调用辅助对象中的方法,而少部分的方法是自己实现的。这就是委托模式的意义所在。

2.1 类委托

类委托核心思想:将一个类的具体实现委托给另一个类去完成。借助于委托模式,我们可以轻松实现一个自己的实现类。如:定义一个MySet,并让他实现Set接口:

class MySet<T>(val helperSet:HashSet<T>):Set<T>{
    override val size: Int
        get() = helperSet.size
    override fun contains(element: T): Boolean = helperSet.contains(element)
 
    override fun containsAll(elements: Collection<T>): Boolean  = helperSet.containsAll(elements)
 
    override fun isEmpty(): Boolean = helperSet.isEmpty()
 
    override fun iterator(): Iterator<T> = helperSet.iterator()
 
}
 
//下面代码相当于上面代码,将操作委托给helperSet
class MySet<T>(val helperSet:HashSet<T>):Set<T> by helperSet{
 
}

这其中接收的一个HashSet参数,这就相当于一个辅助对象;然后在Set接口中的所有的方法的实现中,我们都没有进行自己的实现,而是调用了辅助对象中的相应的方法实现。这就是一种委托模式。

但是如果有成百上千的返方法都要如上代码中那样去调用辅助对象中的相应代码实现,就要哭了。 为了避免这种调用对象中的待实现方法过多的情况,使用的关键字 by加上受委托的辅助对象进行类委托,这样可以省却很多的模板代码编写。

//MySet中有所有Set接口中的功能,和HashSet保持一致, 并且isEmpty是自己实现的
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {

    fun hello() = println("hello")

    //如果你想重写set的某个方法,还能自己重写 ,不想就委托HashSet的用它的
    override fun isEmpty(): Boolean {
        return true
    }

}

2.2 委托属性

委托属性 将一个属性(字段)的具体实现委托给另外一个类去完成委托属性的语法结构:

class MyClass{
    var p by Delegate()
}

lazy函数懒加载技术原理:通过by委托属性然后在调用获取值的时候才调用getValue


三 泛型的高级特性

3.1 泛型实化

java和kotlin都是类型擦除机制,泛型只是对于编译器的类型的约束,运行期是识别不出来我们代码中指定的泛型类型的,所以肯定实现不了 a is T 或者 T::class.java 这样的用法。

但是Koltlin可以利用内联函数和reified关键字将内联函数中的泛型进行实化。例如:inline fun <reified T> getGenricType() = T::class.java

泛型实化的应用:

//用到了内联函数实化,扩展函数,高阶函数
inline fun <reified T> startActivity(context:Context,block:Intent.() -> Unit){
    val intent = Intent(context,T::class.java)
    intent.block()
    context.startActivity()
}
 
//用到了lambda表达式
startActivity<TestActivity>(this){
    putExtra("param1","data")
    putExtra("param2",123)
}

面试问题:为什么Kotlin可以泛型实例化?

泛型实例化要求必须是inline修饰的内联函数中的泛型,而内联函数在进行代码替换后,替换后的代码中是可以获得泛型的实际类型的(因为你泛型T本就是写在了内联函数fun <T> xxx 中的T中)。这时你还用refield关键字来表示要对该泛型进行实例化,当然就可以获得该泛型具体的类型并实例化了。

3.2 泛型的协变

java中不允许List<Student>是List<Person>的子类,原因如下:

class SimpleData<T> {
    
    private var data:T? = null
 
    fun set(t:T?){
        data = T
    }
 
    fun get():T = data
}
 
val student = Student()
val data = SimpleData<Student>()
data.set(student)
val teacher = Teacher()
data.set(teacher)
val studentData = data.get()//此处就存在隐患,不知道是Student还是Teacher

协变:假如A是B的子类型,则MyClass<A>也是MyClass<B>的子类型

实现如下:利用out关键字

class SimpleData<out T>(val data:T) {
 
    fun get():T = data
 
}

3.3 泛型的逆变

如果A是B的子类型,则MyClass<B>是MyClass<A>的子类型

interface Transformer<T> {
    fun transform(t:T):String
}
 
fun main(){
    val trans = object : Transtormer<Person> {
        override fun transform(t:Person):String = "${t.name}${t.age}"
    }
    handleTransformer(trans)//会报错,因为Transtormer<Person>不是Transtormer<Student>子类
}
 
fun handleTransformer(t:Transtormer<Student>){
    val student = Student()
    val result = t.transform(student)
}
 
//逆变修改如下
interface Transformer<in T> {
    fun transform(): @UnsafeVariance T
}
 
fun main(){
    val trans = object : Transtormer<Person> {
        override Teacher()
    }
    handleTransformer(trans)//通过编译,但是写法不对
}
 
fun handleTransformer(t:Transtormer<Student>){
    val student = Student()
    val result = t.transform(student)
}
 

具体源码例子,Comparable用于对比两个对象大小的接口

interface Comparable<in T>{
    operator fun comparaTo(other:T):Int
}

泛型的逆变和协变都涉及到了子类型和父类型之间的关系,它们分别适用于不同的场景:

  1. 逆变(Contravariance super in)

    • 逆变允许将泛型类型参数声明为父类型,这样就可以接受更广泛(更通用)的类型作为参数,而不仅限于子类型。
    • 逆变通常用于消费泛型类型的情况,例如函数参数或者接受泛型类型的方法。
    • 在逆变中,子类型的实例可以替换父类型的实例。
  2. 协变(Covariance extends out)

    • 协变允许将泛型类型参数声明为子类型,这样就可以接受更具体(更特定)的类型作为参数,而不仅限于父类型。
    • 协变通常用于产生泛型类型的情况,例如函数的返回类型或者返回泛型类型的方法。
    • 在协变中,父类型的实例可以替换子类型的实例。

逆变的场景示例: 逆变常见于那些只消费泛型类型的情况,例如比较器(Comparator)。比如,一个能够比较不同类型的动物并根据它们的名称进行排序的比较器。在这种情况下,我们希望比较器能够接受 Animal 的实例,而不仅仅是特定子类(如 Cat 或 Dog)的实例。

协变的场景示例: 协变通常用于那些产生泛型类型的情况,例如容器类(如 List)。例如,我们有一个动物园类,其中有一个方法返回动物的列表。我们希望这个方法返回一个 Animal 的列表,但是实际上它可能返回 Cat 或者 Dog 的列表,因为它们都是 Animal 的子类。

注:本文主要例子和参考

郭霖《第一行代码》 Kotlin部分

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许进进

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值