Kotlin(六)多態(tài)和擴(kuò)展

6.1 多態(tài)的不同方式

當(dāng)我們用一個(gè)類繼承父類時(shí),這就是子類型多態(tài)。另外一種是參數(shù)多態(tài),泛型就是其中的一種表現(xiàn)。還有C++中的運(yùn)算符重載屬于特設(shè)多態(tài)。Kotlin中除了運(yùn)算符重載,還有擴(kuò)展等表現(xiàn)多態(tài)的形式

6.1.1 子類型多態(tài)

看一個(gè)代碼的定義

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的所有方法,這種用子類型替換超類型實(shí)例的行為,就是我們通常說的子類型多態(tài)。

6.1.2 參數(shù)多態(tài)

可能會(huì)這樣寫一個(gè)方法

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

需求會(huì)不斷變動(dòng),每個(gè)類型都想Customer一樣重寫一份persist 不適合。采取的是鍵值對(duì),所以獲取不同類型對(duì)應(yīng)的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 對(duì)第三方進(jìn)行擴(kuò)展

假設(shè)對(duì)應(yīng)的業(yè)務(wù)類ClassA,ClassB是第三方引入的,且不可以被修改。如果想擴(kuò)展一些方法,如轉(zhuǎn)化成json。利用之前的多態(tài)比較麻煩。可以利用擴(kuò)展,可以換一種思路解決問題:

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

java中我們可以用設(shè)計(jì)及模式解決類似擴(kuò)展問題,kotlin的擴(kuò)展,避免修改父類,避免了因?yàn)楦割悈?shù)修改導(dǎo)致子類出錯(cuò)的問題。擴(kuò)展也被叫做特設(shè)多態(tài)的一種技術(shù)

6.1.4 特設(shè)多態(tài)---運(yùn)算符重載

當(dāng)擬定一一個(gè)sum的時(shí)候,可能想這么寫
-> 錯(cuò)誤的例子

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

但是不是說有類型支持 + 法則,所以編譯器報(bào)錯(cuò)。

如果換一種方式,設(shè)計(jì)Summable接口,然后讓支持的加法操作實(shí)現(xiàn)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)
}

如果針對(duì)不可以修改的第三方類擴(kuò)展加法,通過子類型進(jìn)行多態(tài)的思路手段也會(huì)遇到問題

所有用我們的”特設(shè)多態(tài)“運(yùn)算符重載

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 標(biāo)記重載一個(gè)操作符或者實(shí)現(xiàn)一個(gè)約定。
plus是Kotlin內(nèi)置的可重載運(yùn)算符

6.2 為別的類添加方法和屬性

6.2.1 擴(kuò)展與開放封閉原則

修改源碼,我們應(yīng)該遵循開放封閉原則,即軟件是可以擴(kuò)展的不是修改的

開放封閉原則
是所有面向?qū)ο笤瓌t的核心。軟件本身所追求的目標(biāo)就是封裝變化,降低耦合,開閉原則是這個(gè)目標(biāo)的直接體現(xiàn)。其他的設(shè)計(jì)原則有時(shí)候是為了這一目標(biāo)服務(wù),如,替換原則,實(shí)現(xiàn)最佳的,正確的繼承層次,不會(huì)違反開放封閉原則。

實(shí)際上不容樂觀,引入了第三方庫,某天需求發(fā)生變動(dòng),當(dāng)前庫無法滿足,庫的作者暫時(shí)沒更新計(jì)劃,你可能會(huì)嘗試修改庫源碼,這就違背了開放封閉原則。隨著需求不斷變更,就會(huì)把問題滾雪球一樣的放大。

java中的慣常應(yīng)對(duì)方案是讓第三方庫繼承一個(gè)子類,然后添加新功能。然而強(qiáng)行繼承可能會(huì)違背"里氏替換原則"

Kotlin提供的擴(kuò)展,大部分情況都是一種更好的選擇,從而我們可以合理的遵循軟件設(shè)計(jì)原則。

6.2.2 使用擴(kuò)展函數(shù)、屬性

擴(kuò)展函數(shù)的聲明非常簡單,他的關(guān)鍵<Type>. 我們需要一個(gè)接收者(receivier type 通常是類或者接口名)類型作為他的前綴

MutableList<Int> 為例子,擴(kuò)展一個(gè)方法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標(biāo)準(zhǔn)庫中的List容器,作為receievier type,exchange是擴(kuò)展函數(shù)名,其他與普通函數(shù)沒有區(qū)別,這里的this代表的是接受者類型的對(duì)象,kotlin嚴(yán)格控制接收者可空類型,如果你的函數(shù)是可空的,你需要重寫可空類型的擴(kuò)展函數(shù)。

  1. 擴(kuò)展函數(shù)實(shí)現(xiàn)機(jī)制----對(duì)性能影像
@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擴(kuò)展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近似理解為靜態(tài)方法。
Java中靜態(tài)方法特點(diǎn):

    1. 獨(dú)立于該類的任何對(duì)象,且不依賴特定實(shí)例,被該類所有實(shí)例共享
    1. public 修飾的static方法是全局的

結(jié)論:擴(kuò)展函數(shù)不會(huì)帶來額外的性能銷耗。

  1. 擴(kuò)展函數(shù)的作用域

一個(gè)包內(nèi)可以直接調(diào)用exchange,其他包中需要import相應(yīng)的方法。實(shí)際開發(fā)我們會(huì)將擴(kuò)展函數(shù)定義在一個(gè)Class內(nèi)部統(tǒng)一管理。

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

反編譯后你會(huì)發(fā)現(xiàn)

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關(guān)鍵字,所以擴(kuò)展方法在一個(gè)類class內(nèi)部時(shí),我們只能在該類和該類的子類中進(jìn)行調(diào)用。

  1. 擴(kuò)展屬性

給MutableList<Int> 添加一個(gè)判斷是否是偶數(shù)判斷的屬性sumIsEven:

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

val newlist = mutableListOf(2, 2, 4)
    println(newlist.sumIsEven)

如果加上默認(rèn)值會(huì)報(bào)錯(cuò)

會(huì)報(bào)錯(cuò)
val kotlin.collections.MutableList<kotlin.Int>.sumIsEven: Boolean = false
    get() = this.sum() % 2 == 0

因?yàn)閿U(kuò)展屬性沒有實(shí)際將成員插入類中,因此對(duì)擴(kuò)展屬性幕后字段無效,這就是為什么我們默認(rèn)值失敗原因。他的行為只能顯示用getter和setter定義。

幕后字段
kotlin中,如果屬性存在訪問器使用默認(rèn)實(shí)現(xiàn),那么Kotlin會(huì)自動(dòng)提供幕后字段filed,僅用于自定義getter和setter

6.2.3 擴(kuò)展的特殊情況
  1. 類似java的擴(kuò)展函數(shù)

如果要聲明靜態(tài)的擴(kuò)展函數(shù),那么必須定義在半生對(duì)象。

class Son {
    companion object {
        val age = 10
    }
}

fun Son.Companion.foo() {
    println("age=$age")
}

object Test{
    @JvmStatic
    fun main() {
        Son.foo()
    }
}

我們就能在Son沒實(shí)例對(duì)象的情況下,也能調(diào)用到這個(gè)擴(kuò)展函數(shù)。但是想讓第三方庫也可以這樣,并不是所有第三方庫都有半生對(duì)象,我們只能通過他的實(shí)例調(diào)用,但這會(huì)帶來很多不必要麻煩。

  1. 同名的類成員方法優(yōu)先級(jí)高于擴(kuò)展函數(shù)
class Son2{
    fun foo() = println("son memeber foo")
}

fun Son2.foo() = println("son extends foo")

fun main() {
    Son2().foo()
}
> >>
son memeber foo
  1. 類的實(shí)例與接收者的實(shí)例。
    我們說過,擴(kuò)展函數(shù)調(diào)用this,指代的是接收者類型的實(shí)例。
    擴(kuò)展函數(shù)聲明在object內(nèi)部,我們用this@類名,強(qiáng)行指定調(diào)用的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 標(biāo)準(zhǔn)庫的擴(kuò)展函數(shù):run,let,also,takeif
  1. run
fun testFoo(){
    val nickName = "Prefert"
    run {
        val nickName = "David"
        println(nickName)//David
    }
    println(nickName)//Prefert
}

run 函數(shù)我們有一個(gè)單獨(dú)的作用域,能重新定義nickName,并且作用域只在run中

看下源代碼

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

run是任何類型T的擴(kuò)展函數(shù),run中執(zhí)行了返回類型R的擴(kuò)展函數(shù)block,組中返回了擴(kuò)展函數(shù)的結(jié)果

場(chǎng)景:用戶點(diǎn)擊新人領(lǐng)取獎(jiǎng)勵(lì)按鈕,如果用戶沒有登錄則彈出loginDialog,已經(jīng)登錄彈出領(lǐng)取獎(jiǎng)勵(lì)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返回的是原來的對(duì)象,let返回的是閉包里面的值

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

舉個(gè)栗子:

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返回的是閉包的最后一行,當(dāng)Student部位null的時(shí)候,才會(huì)打印并放回他的年齡,和run一樣,他同樣限制了作用域

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

與apply一致,他的返回值是改函數(shù)的接收者

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 {
           ....
        }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

  • 多態(tài)定義 多態(tài)是指允許不同類的對(duì)象對(duì)同一消息做出相應(yīng),即對(duì)同一消息可以根據(jù)發(fā)送對(duì)象的不同而采用不同的行為方式。(發(fā)...
    積跬步以致千里_ylc閱讀 1,252評(píng)論 0 0
  • 1.多態(tài)的不同方式 子類型多態(tài):子類型替換超類型實(shí)例 參數(shù)多態(tài):泛型 對(duì)第三方類進(jìn)行擴(kuò)展 特設(shè)多態(tài):一個(gè)多態(tài)函數(shù)室...
    DreamSunny閱讀 305評(píng)論 0 0
  • Google在今年的IO大會(huì)上宣布,將Android開發(fā)的官方語言更換為Kotlin,作為跟著Google玩兒An...
    玖玖君閱讀 7,737評(píng)論 0 8
  • Java的輝煌與陰影 1995年,當(dāng)年如日中天的Sun公司發(fā)布了Java語言,引起了巨大的轟動(dòng),與當(dāng)時(shí)主流的C語言...
    private_object閱讀 437評(píng)論 0 0
  • Google在今年的IO大會(huì)上宣布,將Android開發(fā)的官方語言更換為Kotlin,作為跟著Google玩兒An...
    藍(lán)灰_q閱讀 77,194評(píng)論 31 489

友情鏈接更多精彩內(nèi)容