我的Kotlin之旅Android · Kotlin · 移动开发 · 性能测试 · 无线技术Kotlin 程序设计

Kotlin(六)多态和扩展

2021-01-08  本文已影响0人  zcwfeng

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. 扩展函数的作用域

一个包内可以直接调用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 {
           ....
        }
上一篇 下一篇

猜你喜欢

热点阅读