Kotlin(六)多态和扩展

作者: zcwfeng | 来源:发表于2021-01-08 00:06 被阅读0次

    6.1 多态的不同方式

    当我们用一个类继承父类时,这就是子类型多态。另外一种是参数多态,泛型就是其中的一种表现。还有C++中的运算符重载属于特设多态。Kotlin中除了运算符重载,还有扩展等表现多态的形式

    6.1.1 子类型多态

    看一个代码的定义

    class CustomerDatabaseHelper(context: Context)
        : SQLiteOpenHelper(context, "kotlinDemo.db",
            cursorFactory, version
    ) {
        override fun onCreate(db: SQLiteDatabase?) {
            val sql = "create table if not exists StableName(id integer PRIMARY KEY autoincrement,uniqueKey varchar(32))"
            db?.execSQL(sql)
        }
    
        override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        }
    }
    

    使用父类DatabaseHelper的所有方法,这种用子类型替换超类型实例的行为,就是我们通常说的子类型多态。

    6.1.2 参数多态

    可能会这样写一个方法

    fun persist(customer:Customer){
        db.save(customer.uniqueKey,customer)
    }
    

    需求会不断变动,每个类型都想Customer一样重写一份persist 不适合。采取的是键值对,所以获取不同类型对应的uniqueKey

    interface KeyI{
        val uniqueKey : String
    }
    class ClassA(override val uniqueKey:String):KeyI{
    
    }
    
    class ClassB(override val uniqueKey:String):KeyI{
    
    }
    

    A,B都具备uniqueKey,改写persist 方法,最常见的泛型

    fun <T:KeyI> persist(t:T){
        db.save(t.uniqueKey,t)
    }
    
    6.1.3 对第三方进行扩展

    假设对应的业务类ClassA,ClassB是第三方引入的,且不可以被修改。如果想扩展一些方法,如转化成json。利用之前的多态比较麻烦。可以利用扩展,可以换一种思路解决问题:

    fun ClassA.toJson():String = {
    ...
    }
    

    java中我们可以用设计及模式解决类似扩展问题,kotlin的扩展,避免修改父类,避免了因为父类参数修改导致子类出错的问题。扩展也被叫做特设多态的一种技术

    6.1.4 特设多态---运算符重载

    当拟定一一个sum的时候,可能想这么写
    -> 错误的例子

    fun <T> sum(x:T,y:T) : T = x + y
    

    但是不是说有类型支持 + 法则,所以编译器报错。

    如果换一种方式,设计Summable接口,然后让支持的加法操作实现plusThat

    interface Summable<T>{
        fun plusThat(that:T):T
    }
    
    data class Len(val v:Int):Summable<Len>{
        override fun plusThat(that: Len)= Len(this.v+that.v)
    }
    

    如果针对不可以修改的第三方类扩展加法,通过子类型进行多态的思路手段也会遇到问题

    所有用我们的”特设多态“运算符重载

    data class Area(val value: Double)
    
    operator fun Area.plus(that: Area): Area {
        return Area(this.value + that.value)
    }
    fun main() {
        println(Area(1.0) + Area(2.0))
    }
    

    kotlin通过operator 标记重载一个操作符或者实现一个约定。
    plus是Kotlin内置的可重载运算符

    6.2 为别的类添加方法和属性

    6.2.1 扩展与开放封闭原则

    修改源码,我们应该遵循开放封闭原则,即软件是可以扩展的不是修改的

    开放封闭原则
    是所有面向对象原则的核心。软件本身所追求的目标就是封装变化,降低耦合,开闭原则是这个目标的直接体现。其他的设计原则有时候是为了这一目标服务,如,替换原则,实现最佳的,正确的继承层次,不会违反开放封闭原则。

    实际上不容乐观,引入了第三方库,某天需求发生变动,当前库无法满足,库的作者暂时没更新计划,你可能会尝试修改库源码,这就违背了开放封闭原则。随着需求不断变更,就会把问题滚雪球一样的放大。

    java中的惯常应对方案是让第三方库继承一个子类,然后添加新功能。然而强行继承可能会违背"里氏替换原则"

    Kotlin提供的扩展,大部分情况都是一种更好的选择,从而我们可以合理的遵循软件设计原则。

    6.2.2 使用扩展函数、属性

    扩展函数的声明非常简单,他的关键<Type>. 我们需要一个接收者(receivier type 通常是类或者接口名)类型作为他的前缀

    MutableList<Int> 为例子,扩展一个方法exchange:

    fun MutableList<Int>.exchange(fromIndex:Int,toIndex:Int){
        val tmp = this[fromIndex]
        this[fromIndex] = this[toIndex]
        this[toIndex] = tmp;
    }
    
    fun main() {
        val list = mutableListOf(1,2,3)
        list.exchange(0,2)
        println(list)
    }
    

    MutableList<T> 是Kotlin 的Collections标准库中的List容器,作为receievier type,exchange是扩展函数名,其他与普通函数没有区别,这里的this代表的是接受者类型的对象,kotlin严格控制接收者可空类型,如果你的函数是可空的,你需要重写可空类型的扩展函数。

    1. 扩展函数实现机制----对性能影像
    @Metadata(
       mv = {1, 1, 16},
       bv = {1, 0, 3},
       k = 2,
       xi = 2,
       d1 = {"\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u001a \u0010\u0002\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00040\u00032\u0006\u0010\u0005\u001a\u00020\u00042\u0006\u0010\u0006\u001a\u00020\u0004¨\u0006\u0007"},
       d2 = {"main", "", "exchange", "", "", "fromIndex", "toIndex", "use_kt_poject.main"}
    )
    public final class KT扩展Kt {
       public static final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
          Intrinsics.checkNotNullParameter($this$exchange, "$this$exchange");
          int tmp = ((Number)$this$exchange.get(fromIndex)).intValue();
          $this$exchange.set(fromIndex, $this$exchange.get(toIndex));
          $this$exchange.set(toIndex, tmp);
       }
    
       public static final void main() {
          List list = CollectionsKt.mutableListOf(new Integer[]{1, 2, 3});
          exchange(list, 0, 2);
          boolean var1 = false;
          System.out.println(list);
       }
    
       // $FF: synthetic method
       public static void main(String[] var0) {
          main();
       }
    }
    

    反编译后可以看到exchange近似理解为静态方法。
    Java中静态方法特点:

      1. 独立于该类的任何对象,且不依赖特定实例,被该类所有实例共享
      1. public 修饰的static方法是全局的

    结论:扩展函数不会带来额外的性能销耗。

    1. 扩展函数的作用域

    一个包内可以直接调用exchange,其他包中需要import相应的方法。实际开发我们会将扩展函数定义在一个Class内部统一管理。

    class Extends{
    fun MutableList<Int>.exchange(fromIndex:Int,toIndex:Int){
        val tmp = this[fromIndex]
        this[fromIndex] = this[toIndex]
        this[toIndex] = tmp;
    }
    }
    

    反编译后你会发现

    public final class Extends {
       public final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
          Intrinsics.checkNotNullParameter($this$exchange, "$this$exchange");
          int tmp = ((Number)$this$exchange.get(fromIndex)).intValue();
          $this$exchange.set(fromIndex, $this$exchange.get(toIndex));
          $this$exchange.set(toIndex, tmp);
       }
    

    没有了static关键字,所以扩展方法在一个类class内部时,我们只能在该类和该类的子类中进行调用。

    1. 扩展属性

    给MutableList<Int> 添加一个判断是否是偶数判断的属性sumIsEven:

    val kotlin.collections.MutableList<kotlin.Int>.sumIsEven: Boolean
        get() = this.sum() % 2 == 0
    
    val newlist = mutableListOf(2, 2, 4)
        println(newlist.sumIsEven)
    

    如果加上默认值会报错

    会报错
    val kotlin.collections.MutableList<kotlin.Int>.sumIsEven: Boolean = false
        get() = this.sum() % 2 == 0
    

    因为扩展属性没有实际将成员插入类中,因此对扩展属性幕后字段无效,这就是为什么我们默认值失败原因。他的行为只能显示用getter和setter定义。

    幕后字段
    kotlin中,如果属性存在访问器使用默认实现,那么Kotlin会自动提供幕后字段filed,仅用于自定义getter和setter

    6.2.3 扩展的特殊情况
    1. 类似java的扩展函数

    如果要声明静态的扩展函数,那么必须定义在半生对象。

    class Son {
        companion object {
            val age = 10
        }
    }
    
    fun Son.Companion.foo() {
        println("age=$age")
    }
    
    object Test{
        @JvmStatic
        fun main() {
            Son.foo()
        }
    }
    

    我们就能在Son没实例对象的情况下,也能调用到这个扩展函数。但是想让第三方库也可以这样,并不是所有第三方库都有半生对象,我们只能通过他的实例调用,但这会带来很多不必要麻烦。

    1. 同名的类成员方法优先级高于扩展函数
    class Son2{
        fun foo() = println("son memeber foo")
    }
    
    fun Son2.foo() = println("son extends foo")
    
    fun main() {
        Son2().foo()
    }
    > >>
    son memeber foo
    
    1. 类的实例与接收者的实例。
      我们说过,扩展函数调用this,指代的是接收者类型的实例。
      扩展函数声明在object内部,我们用this@类名,强行指定调用的this.
    class Son2{
        fun foo() = println("son in class Son")
    }
    
    object Parent{
        fun foo() = println("son in Parent Son")
    
        @JvmStatic
        fun main(args: Array<String>) {
            fun Son2.foo2(){
                this.foo()
                this@Parent.foo()
            }
            Son2().foo2()
        }
    }
    >>>>>
    son in class Son
    son in Parent Son
    
    6.2.4 标准库的扩展函数:run,let,also,takeif
    1. run
    fun testFoo(){
        val nickName = "Prefert"
        run {
            val nickName = "David"
            println(nickName)//David
        }
        println(nickName)//Prefert
    }
    

    run 函数我们有一个单独的作用域,能重新定义nickName,并且作用域只在run中

    看下源代码

    public inline fun <T, R> T.run(block: T.() -> R): R {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        return block()
    }
    

    run是任何类型T的扩展函数,run中执行了返回类型R的扩展函数block,组中返回了扩展函数的结果

    场景:用户点击新人领取奖励按钮,如果用户没有登录则弹出loginDialog,已经登录弹出领取奖励getNewAccountDialog,可以用如下代码

    run{
      if(!isLogin) loginDialog else getNewAccountDialog
    }.show()
    
    1. let
    public inline fun <T, R> T.let(block: (T) -> R): R {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        return block(this)
    }
    

    前面的文章我们提到过apply,let和apply类似,唯一不同是返回值:apply返回的是原来的对象,let返回的是闭包里面的值

    public inline fun <T> T.apply(block: T.() -> Unit): T {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        block()
        return this
    }
    

    举个栗子:

    data class StudentA(val age: Int)
    class Kot{
        val student:StudentA? = getStu()
        fun dealStu(){
            val result = student?.let{
                println(it.age)
                it.age
            }
        }
    }
    
    fun getStu():StudentA{
        return StudentA(11)
    }
    
    

    let返回的是闭包的最后一行,当Student部位null的时候,才会打印并放回他的年龄,和run一样,他同样限制了作用域

    1. also ,可以看做let和apply的加强版
    public inline fun <T> T.also(block: (T) -> Unit): T {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        block(this)
        return this
    }
    

    与apply一致,他的返回值是改函数的接收者

    class Kot{
        var age = 0
        val student:StudentA? = getStu()
        fun dealStu(){
            val result = student?.also{stu->
                this.age += stu.age
                println(this.age)
                println(stu.age)
                stu.age
            }
        }
    }
    

    also 返回student,并且年龄age增加
    如果换成also---》apply 我们将无法访问Kot 下的age

    4.takeIf

    public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
        contract {
            callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
        }
        return if (predicate(this)) this else null
    }
    

    如果我们不仅仅想判断空,还要加入条件,那么用takeIf

    val result = student.takeIf { it.age >=18 }.let {
               ....
            }
    

    相关文章

      网友评论

        本文标题:Kotlin(六)多态和扩展

        本文链接:https://www.haomeiwen.com/subject/yhtqoktx.html