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阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容