<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                擴展是Kotlin實現特設多態的一種非常重要的語言特性。在本節中,我們將繼續探討這種技術。 [TOC] ### 擴展與開放封閉原則 對開發者而言,業務需求總是在不斷變動的。熟悉設計模式的讀者應該知道,在修改現有代碼的時候,我們應該遵循開放封閉原則,即:**軟件實體應該是可擴展,而不可修改的**。也就是說,對擴展開放,而對修改是封閉的。 ***** **開放封閉原則概念** **開放封閉原則(OCP, Open Closed Principle)是所有面向對象原則的核心**。軟件設計本身所追求的目標就是封裝變化、降低耦合,而開放封閉原則正是對這一目標的最直接體現。其他的設計原則,很多時候是為實現這一目標服務的,例如以替換原則實現最佳的、正確的繼承層次,就能保證不會違反開放封閉原則。 ***** 實際情況并不樂觀,比如在進行Android開發的時候,為了實現某個需求,我們引入了一個第三方庫。但某一天需求發生了變動,當前庫無法滿足,且庫的作者暫時沒有升級的計劃。這時候也許你就會開始嘗試對庫源碼進行修改。這就違背了開放封閉原則。隨著需求的不斷變更,問題可能就會如滾雪球般增長。 Java中一種慣常的應對方案是讓第三方庫類繼承一個子類,然后添加新功能。然而,正如我們談論過的那樣,強行的繼承可能違背“里氏替換原則”。 **更合理的方案是依靠擴展這個語言特性。Kotlin通過擴展一個類的新功能而無須繼承該類,在大多數情況下都是一種更好的選擇,從而我們可以合理地遵循軟件設計原則**。 ### 使用擴展函數、屬性 擴展函數的聲明非常簡單,它的關鍵字是`<Type>`。此外我們需要一個“接收者類型(recievier 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(args: Array<String>) { val list = mutableListOf(1, 2, 3) list.exchange(1, 2) } ``` `MutableList<T>`是Kotlin標準庫Collections中的List容器類,這里作為接收者recievier type,exchange是擴展函數名。其余和Kotlin聲明一個普通函數并無區別。Kotlin的this要比Java更靈活,**這里擴展函數體里的this代表的是接收者類型的對象**。 這里需要注意的是:**Kotlin嚴格區分了接收者是否可空。如果你的函數是可空的,你需要重寫一個可空類型的擴展函數**。 我們可以非常方便地對該函數進行調用,代碼如下: ``` val list = mutableListOf(1,2,3) list.exchange(1,2) ``` #### 1.擴展函數的實現機制 擴展函數的使用如此方便,會不會對性能造成影響呢?為了解決這個疑惑,我們有必要對Kotlin擴展函數的實現進行探究。我們以之前的`MutableList<Int>.exchange`為例,它對應的Java代碼如下: ``` import java.util.List; import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import org.jetbrains.annotations.NotNull; @Metadata( mv = {1, 1, 1}, bv = {1, 0, 0}, k = 2, d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010! \n\u0002\ u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\ u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\ u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006"}, d2 = {"exchange", "", "", "", "fromIndex", "toIndex", "production sources for module FPKotlin"} ) public final class ExSampleKt { public static final void exchange(@NotNull List $receiver, int fromIndex, int toIndex) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); int tmp = ((Number)$receiver.get(fromIndex)).intValue(); $receiver.set(fromIndex, $receiver.get(toIndex)); $receiver.set(toIndex, Integer.valueOf(tmp)); } } ``` 結合以上Java代碼可以看出,我們**可以將擴展函數近似理解為靜態方法。而熟悉Java的讀者應該知道靜態方法的特點:它獨立于該類的任何對象,且不依賴類的特定實例,被該類的所有實例共享。此外,被public修飾的靜態方法本質上也就是全局方法**。 綜上所述,我們可以得出結論:**擴展函數不會帶來額外的性能消耗**。 #### 2.擴展函數的作用域 既然擴展函數不會帶來額外的性能消耗,那我們就可以放心地使用它。它的作用域范圍是怎么樣的呢?**一般來說,我們習慣將擴展函數直接定義在包內**,例如之前的exchange例子,我們可以將其放在com.example.extension包下: ``` package com.example.extension fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) { val tmp = this[fromIndex] this[fromIndex] = this[toIndex] this[toIndex] = tmp } ``` 我們知道在同一個包內是可以直接調用exchange方法的。如果需要在其他包中調用,只需要import相應的方法即可,這與調用Java全局靜態方法類似。除此之外,實際開發時我們也可能會將擴展函數定義在一個Class內部統一管理。 ``` class Extends { fun MutableList<Int>.exchange(fromIndex:Int, toIndex:Int) { val tmp = this[fromIndex] this[fromIndex] = this[toIndex] this[toIndex] = tmp } } ``` 當擴展函數定義在Extends類內部時,情況就與之前不一樣了:這個時候你會發現,之前的exchange方法無法調用了(之前調用位置在Extends類外部)。你可能會猜想,是不是它被聲明為private方法了?那我們嘗試在exchange方法前加上public關鍵字: ``` public fun MutableList<Int>.exchange(fromIndex:Int, toIndex:Int) { … } ``` 結果不盡如人意,此時我們依舊無法調用到(實際上Kotlin中成員方法默認就是用public修飾的)。是什么原因呢?借助IDEA我們可以查看到它對應的Java代碼,這里展示關鍵部分: ``` public static final class Extends { public final void exchange(@NotNull List $receiver, int fromIndex, int toIndex) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); int tmp = ((Number)$receiver.get(fromIndex)).intValue(); $receiver.set(fromIndex, $receiver.get(toIndex)); $receiver.set(toIndex, Integer.valueOf(tmp)); } } ``` 我們看到,exchange方法上已經沒有static關鍵字的修飾了。所以**當擴展方法在一個Class內部時,我們只能在該類和該類的子類中進行調用**。此外你可能還會想到:如果我用private修飾這個擴展函數,又會有什么結果?這個問題留給讀者自行探究。 #### 擴展屬性 與擴展函數類似,我們還能為一個類添加擴展屬性。比如我們想給`MutableList<Int>`添加判斷一個判斷和是否為偶數的屬性sumIsEven: ``` val MutableList<Int>.sumIsEven: Boolean get() = this.sum() % 2 == 0 ``` 這樣就可以像調用擴展函數一樣調用它了: ``` val list = mutableListOf(2,2,4) list.sumIsEven ``` 但是,如果你準備給這個屬性添加上默認值,并且寫出如下代碼: // 編譯錯誤:擴展屬性不能有初始化器 ``` val MutableList<Int>.sumIsEven: Boolean = false get() = this.sum() % 2 == 0 ``` 以上代碼編譯不能通過,這是為什么呢? 其實,與擴展函數一樣,其本質也是對應Java中的靜態方法(我們反編譯成Java代碼后可以看到一個getSumIsEven的靜態方法,與擴展函數類似)。**由于擴展沒有實際地將成員插入類中,因此對擴展屬性來說幕后字段是無效的。這就是為什么擴展屬性不能有初始化器的原因。它們的行為只能由顯式提供的getters和setters定義**。 ***** **幕后字段** 在Kotlin中,如果屬性中存在訪問器使用默認實現,那么Kotlin會自動提供幕后字段filed,其僅可用于自定義getter和setter中。 ***** ### 擴展的特殊情況 前面,我們對Kotlin的擴展函數已經有了基本的認識,相信大部分讀者已經被擴展函數所吸引,并且已經想好如何利用擴展函數進行實戰。但在此之前,還是讓我們先看一些擴展中特殊的情況,或者說是擴展的局限之處。 #### 1.類似Java的靜態擴展函數 在Kotlin中,如果你需要聲明一個靜態的擴展函數,開發者**必須將其定義在伴生對象(companion object)上**。所以我們需要這樣定義帶有伴生對象的類: ``` class Son { companion object { val age = 10 } } ``` Son類中已經有一個伴生對象,如果我們現在**不想在Son中定義擴展函數,而是在Son的伴生對象上定義**,可以這么寫: ``` fun Son.Companion.foo() { println("age = $age") } ``` 這樣,**我們就能在Son沒有實例對象的情況下,也能調用到這個擴展函數,語法類似于Java的靜態方法**。 ``` object Test { @JvmStatic fun main(args: Array<String>) { Son.foo() } } ``` 一切看起來都很順利,但是當我們想讓第三方類庫也支持這樣的寫法時,我們發現,并**不是所有的第三方類庫中的類都存在伴生對象,我們只能通過它的實例來進行調用,但這樣會造成很多不必要的麻煩**。 #### 2.成員方法優先級總高于擴展函數 已知我們有如下類: ``` class Son { fun foo() = println("son called member foo") } ``` 它包含一個成員方法foo(),假如我們哪天心血來潮,想對這個方法做特殊實現,利用擴展函數可能會寫出如下代碼: ``` fun Son.foo() = println("son called extention foo") object Test { @JvmStatic fun main(args: Array<String>) { Son().foo() } } ``` 在我們的預期中,我們希望調用的是擴展函數foo(),但是輸出結果為: son called member foo。這表明:**當擴展函數和現有類的成員方法同時存在時,Kotlin將會默認使用類的成員方法**。看起來似乎不夠合理,并且很容易引發一些問題:我定義了新的方法,為什么還是調用到了舊的方法? 但是換一個角度思考,**在多人開發的時候,如果每個人都對Son擴展了foo方法,是不是很容易造成混淆。對于第三方類庫來說甚至是一場災難:我們把不應該更改的方法改變了**。所以在使用時,我們必須注意:**同名的類成員方法的優先級總高于擴展函數**。 #### 3.類的實例與接收者的實例 前面的例子中提到過,我們發現Kotlin中的this比在Java中更靈活。以擴展函數為例,**當在擴展函數里調用this時,指代的是接收者類型的實例**。那么如果這個擴展函數聲明在一個object內部,我們如何通過this獲取到類的實例呢?參考如下代碼: ``` class Son{ fun foo(){ println("foo in Class Son") } } object Parent { fun foo() { println("foo in Class Parent") } @JvmStatic fun main(args: Array<String>) { fun Son.foo2() { this.foo() this@Parent.foo() } Son().foo2() } } ``` 這里我們**可以用this@類名來強行指定調用的this**。另外值得一提的是:**如果Son擴展函數在Parent類內,我們將無法對其調用**。 ``` class Son{ fun foo(){ println("foo in Class Son") } } class Parent { fun foo() { println("foo in Class Parent") } fun Son.foo2() { this.foo() this@Parent.foo() } } object Test { @JvmStatic fun main(args: Array<String>) { Son().foo2() } } ``` 這是為什么呢?來看看Parent對應的Java代碼,以下為核心部分: ``` public final class Parent { public final void foo() { String var1 = "foo in Class Parent"; System.out.println(var1); } public final void foo2(@NotNull Son $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); $receiver.foo(); this.foo(); } } ``` 即使我們設置訪問權限為public,它也只能在該類或者該類的子類中被訪問,如果我們設置訪問權限為private,那么在子類中也不能訪問這個擴展函數。 ### 標準庫中的擴展函數:run、let、also、takeIf Kotlin標準庫中有一些非常實用的擴展函數,除了之前我們接觸過的apply、with函數之外,我們再來了解下let、run、also、takeIf。 #### 1. run 先來看下run方法,它是利用擴展實現的,定義如下: ``` public inline fun <T, R> T.run(block: T.() -> R): R = block() ``` 簡單來說,run是任何類型T的通用擴展函數,run中執行了返回類型為R的擴展函數block,最終返回該擴展函數的結果。 在run函數中我們擁有一個單獨的作用域,能夠重新定義一個nickName變量,并且它的作用域只存在于run函數中。 ``` fun testFoo() { val nickName = "Prefert" run { val nickName = "YarenTang" println(nickName) // YarenTang } println(nickName) // Prefert } ``` 這個范圍函數本身似乎不是很有用。但是相比范圍,還有一點不錯的是,它返回范圍內最后一個對象。 例如現在有這么一個場景:用戶點擊領取新人獎勵的按鈕,如果用戶此時沒有登錄則彈出loginDialog,如果已經登錄則彈出領取獎勵的getNewAccountDialog。我們可以使用以下代碼來處理這個邏輯: ``` run { if (! islogin) loginDialog else getNewAccountDialog }.show() ``` #### 2. let 在介紹可空類型的時候,我們接觸了let方法,來看看它的定義: ``` public inline fun <T, R> T.let(block: (T) -> R): R = block(this) ``` **let和apply類似,唯一不同的是返回值:apply返回的是原來的對象,而let返回的是閉包里面的值**。細心的讀者應該察覺到,我們在介紹可空類型的時候,大量使用了let語法,簡單回顧一下: ``` data class Student(age: Int) class Kot { val student: Student? = getStu() fun dealStu() { val result = student? .let { println(it.age) it.age } } } ``` **由于let函數返回的是閉包的最后一行,當student不為null的時候,才會打印并返回它的年齡**。與run一樣,它**同樣限制了變量的作用域**。 #### 3. also also是Kotlin 1.1版本中新加入的內容,它**像是let和apply函數的加強版**。 ``` public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this } ``` 與apply一致,它的返回值是該函數的接收者: ``` class Kot { val student: Student? = getStu() var age = 0 fun dealStu() { val result = student? .also { stu -> this.age += stu.age println(this.age) println(stu.age) this.age } } } ``` 我將它的隱式參數指定為stu,假設student?不為空,我們會發現返回了student,并且總年齡age增加了。 值得注意的是:**如果使用apply,由于它內部是一個擴展函數,this將指向stu而不是Kot,此處我們將無法調用到Kot下的age**。 #### 4. takeIf 如果我們**不僅僅只想判空,還想加入條件**,這時let可能顯得有點不足。讓我們來看看takeIf。 ``` public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null ``` 這個函數也是在Kotlin1.1中新增的。當接收器滿足某些條件時它才會執行。如果我們想對成年的學生操作,可以這樣寫: ``` val result = student.takeIf { it.age >= 18 }.let { ... } ``` 我們發現,這**與集合中的filter異曲同工,不過takeIf只操作單條數據**。與takeIf相反的還有takeUnless,即接收器不滿足特定條件才會執行。 除了這些函數外,Kotlin標準庫中還有很多方便的擴展函數。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看