Kotlin學習筆記之 16 委托

首發于公眾號: DSGtalk1989

16.委托

  • 委托實現

    一般直接使用: Class by c來做委托實現,大致的意思就是

    class Derived(b: Base) : Base by b
    
    fun main() {
          val b = BaseImpl(10)
          Derived(b).print()
      }
    

    有很多類都實現了或者繼承了Base,具體Derived要如何去實現Base的抽象方法,不單獨定義,直接委托給b,也就是說Derived的抽象方法實現就是去調b的抽象方法,即上面的BaseImpl方法print

  • 屬性委托

    屬性一般委托給重載操作符getValuesetValue的類,此處我們先不要過多的去在意什么是重載操作符,我們只要記得操作符的重載寫法一般是operator fun

    class Delegate {
          operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
              return "$thisRef, thank you for delegating '${property.name}' to me!"
          }
       
          operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
              println("$value has been assigned to '${property.name}' in $thisRef.")
          }
      }
    

    對于上述的getValuesetValue方法,我們可能單獨去記的話特別痛苦,騎士只需要實現兩個接口即可。

    public interface ReadOnlyProperty<in R, out T> {
          public operator fun getValue(thisRef: R, property: KProperty<*>): T
      }
      
    public interface ReadWriteProperty<in R, T> {
          public operator fun getValue(thisRef: R, property: KProperty<*>): T
      
          public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
      }
    

    這種做法可以讓你在設置或者獲取屬性的屬性做一些自己額外的處理。

    val a : String by Delegate()
    
  • 映射(map)代理

    直接通過map定義的方式,來給類進行屬性賦值。

    class User(val map: Map<String, Any?>) {
          val name: String by map
          val age: Int     by map
      }
      
      val user = User(mapOf(
          "name" to "John Doe",
          "age"  to 25
      ))
    
  • 延遲屬性lazy

    很多時候我們會需要數據只初始化一次,其他的時候我們直接拿來用就OK,這個時候就需要用到延遲屬性。

    val lazyValue: String by lazy {
          println("computed!")
          "Hello"
      }
    

    類似于這一類的lambda,最終取得都是最后一行的結果,所以這個lazy代理的就是String屬性,我們在取lazyValue的時候,會打印一次computed!,之后每一次都只是取到值Hello

  • lazy的延遲模式

    這里需要說明一點,延遲屬性的在初始化的過程中默認是會上鎖的,也就是說如果有多個線程同時去調延遲屬性的話,會出現其他線程在初始化過程中無法調用的情況。

    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    
       public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
       when (mode) {
           LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
           LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
           LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
       }
    

    即我們可以在lazy后面傳入具體的mode方法,從上面的代碼中我們可以看到默認的是LazyThreadSafetyMode.SYNCHRONIZED,還有另外兩個LazyThreadSafetyMode.PUBLICATION,和LazyThreadSafetyMode.NONE,我們來逐一看一下他們的注解說明.

    public enum class LazyThreadSafetyMode {
    
           /**
            * Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
            */
           SYNCHRONIZED,
       
           /**
            * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
            * but only the first returned value will be used as the value of [Lazy] instance.
            */
           PUBLICATION,
       
           /**
            * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
            *
            * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
            */
           NONE,
    }
    
    • SYNCHRONIZED

      首先是默認的SYNCHRONIZED,上鎖為了保證只有一條線程可去初始化lazy屬性。也就是說同時多線程進行訪問該延遲屬性時,一旦沒有初始化好,其他線程將無法訪問。

    fun main() {
    val lazyValue: String by lazy{
    println("start" + Thread.currentThread().name)

          Thread.sleep(5000)
    
          println("end" + Thread.currentThread().name)
    
          "Hello" + Thread.currentThread().name
      }
    
      Thread {
          println(lazyValue)
      }.start()
    
      Thread {
          println(lazyValue)
      }.start()
    
      println(lazyValue)
    

    }

    
      控制臺輸出的是
    
      ```js
    startThread-0
     endThread-0
     HelloThread-0
     HelloThread-0
     HelloThread-0
    

    即第一個線程先進行初始化,其他線程都被堵塞,直到第一個線程完成初始化之后得到了lazyValue,其他線程才可以拿來用。

  • PUBLICATION

    再來看第二個,解釋的意思是對于還沒有被初始化的lazy對象,初始化的方法可以被不同的線程調用很多次,直到有一個線程初始化先完成,那么其他的線程都將使用這個初始化完成的值。

    我們將上面的lazy改成lazy(LazyThreadSafetyMode.PUBLICATION)來看一下控制臺輸出結果。

startThread-0
startThread-1
startmain
endThread-0
endmain
endThread-1
HelloThread-0
HelloThread-0
HelloThread-0
 ```
 
 我們能看到三個線程同時開始了初始化,但是線程`Thread-0`先完成了初始化,得到了延遲屬性的值為`HelloThread-0`,所以后面兩個緊接著完成的線程都使用這個值。
  • NONE

    最后一個,不會對任何訪問和初始化上鎖,也就是說完全放任,官方是這么描述的

    如果你確定初始化將總是發生在單個線程,那么你可以使用LazyThreadSafetyMode.NONE 模式, 它不會有任何線程安全的保證以及相關的開銷

    主要就是為了節省開銷,因為他是線程不安全的。

    我們將上面的lazy改成lazy(LazyThreadSafetyMode.NONE)來看一下控制臺輸出結果。

    startThread-0
       startmain
       startThread-1
       endThread-0
       endmain
       endThread-1
       Hellomain
       HelloThread-0
       HelloThread-1
    

    能夠看到各個線程都產生了各自的結果,也就是說自己玩自己的,明明是Thread-0先完成,但是最先出來的結果是Hellomain,所以將變得完全不可控。但是我們嘗試著在代碼最后再加一個延遲展示

    Thread.sleep(2000)
    println(lazyValue)
    

    打印出來為HelloThread-1可以發現,最終的結果將以最后一次為準。

    也就是說雖然線程是不安全的,但是一旦經過這一團混亂的初始化后,最后完成初始化的那個線程得到的結果將是最終的結果。此后將以這個結果為準。

  • 局部委托屬性

    這個是kotlin 1.1 之后開始引入的委托方式。即我們在方法內部可以直接委托屬性。

    上面我們說到lazy的使用方式是后面直接跟lambda表達式,在kotlin中方法可以直接當成參數進行傳遞,一旦需要直接將方法跟在后面我們就用lambda的方式。舉個例子

    val initFun = {
       "init"
    }
    val memoizedFoo1 by lazy(initFun)
    
    val memoizedFoo2 by lazy{
       "init"
    }
    
    

    上面的兩種lazy使用方式實際上是一樣的。

    在kotlin中,如果傳參中最后一個參數是方法,則可以放到參數括號外通過lambda給到。

    如果只有一個函數參數的話,可以省去圓括號,直接用大括號的lambda表達式

    官方使用了比較繞的lazy方式來進行局部屬性委托

    class Foo {
          fun isValid(): Boolean {
              return Random().nextBoolean()
          }
      
          fun doSomething() {
              println("doSomething")
          }
      }
      fun example(computeFoo: () -> Foo) {
          val memoizedFoo by lazy(computeFoo) //memoizedFoo: Foo
          if (memoizedFoo.isValid()) {
              memoizedFoo.doSomething()
          }
      }
    

    example方法需要傳入一個無參的有數據返回的方法,這個方法正好符合上面說的lazy方法要求,因此我們在example的方法中生成一個屬性,并且委托給這個computeFoo,也就是說這個方法computeFoo只要跑完一遍就不會再跑了,即memoizedFoo只要確定指向了一個地址,就不會再變了。當我們之后再反復調example方法的時候,memoizedFoo都不會變了。


Kotlin學習筆記之 1 基礎語法

Kotlin學習筆記之 2 基本數據類型

Kotlin學習筆記之 3 條件控制

Kotlin學習筆記之 4 循環控制

Kotlin學習筆記之 5 類和對象

Kotlin學習筆記之 6 繼承

Kotlin學習筆記之 7 接口

Kotlin學習筆記之 8 擴展

Kotlin學習筆記之 9 數據類與密封類

Kotlin學習筆記之 10 泛型

Kotlin學習筆記之 11 枚舉類

Kotlin學習筆記之 12 對象表達式和對象聲明

Kotlin學習筆記之 13 基礎操作符run、with、let、also、apply

Kotlin學習筆記之 14 包與導入

Kotlin學習筆記之 15 伴生對象

Kotlin學習筆記之 16 委托

Kotlin學習筆記之 17 可觀察屬性

Kotlin學習筆記之 18 函數

Kotlin學習筆記之 19 高階函數與 lambda 表達式

Kotlin學習筆記之 20 內聯函數

Kotlin學習筆記之 21 解構聲明

Kotlin學習筆記之 22 集合

Kotlin學習筆記之 23 相等判斷

Kotlin學習筆記之 24 操作符重載

Kotlin學習筆記之 25 異常捕捉

Kotlin學習筆記之 26 反射

Kotlin學習筆記之 27 類型別名

Kotlin學習筆記之 28 協程基礎

Kotlin學習筆記之 29 上下文與調度器

Kotlin學習筆記之 30 協程取消與超時

Kotlin學習筆記之 31 協程掛起函數的組合

Kotlin學習筆記之 32 協程異常處理

Kotlin學習筆記之 33 協程 & Retrofit

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容