Kotlin的by 委托

Kotlin的by 委托

1. by lazy的原理解析

我们用kotlin经常会用到by lazy,所以我之前一直以为这俩是必须一起用的,但其实bylazy是拆开的,像下面这段代码:

class By {
    val tag by lazy {
        "hello"
    }
}

可以按照下面的格式来理解上面的代码

val / var <property name>: <Type> by <delegate>

kotlin转成java之后的代码如下:

@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002 ... "},
   d2 = {"Ldelegate/By;", "", "()V", "tag", "", "getTag", "()Ljava/lang/String;", "tag$delegate", "Lkotlin/Lazy;", "TestKotlin"}
)
public final class By {
   static final KProperty[] $$delegatedProperties = ...
   @NotNull
   private final Lazy tag$delegate;

   @NotNull
   public final String getTag() {
      Lazy var1 = this.tag$delegate;
      KProperty var3 = $$delegatedProperties[0];
      return (String)var1.getValue();
   }

   public By() {
      this.tag$delegate = LazyKt.lazy((Function0)null.INSTANCE);
   }
}

. 在tag$delegate属性后面有后缀$delegate
. 注意tag$delegate的类型是Lazy,而不是String
. 在By构造方法里面把LazyKt.lazy赋值给tag$delegate
. 所以getTag返回值为给定的代码块的执行结果

感觉还是不是很清晰,继续从Kotlin源码角度看Lazy的实现逻辑:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }
    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

实现逻辑如下:

  1. 默认为SynchronizedLazyImpl实现,initializer是lambda表达式,比如最上面的Lazy的lambda为{"hello"}
  2. 默认值赋值为UNINITIALIZED_VALUE,如果不为默认值则直接返回,说明已经赋值过了。
  3. 后面的赋值方法由Synchronized包裹,支持多线程访问,先检查是否为UNINITIALIZED_VALUE,不为默认值直接返回
  4. 未赋值过,先执行initializer,返回值赋值给typedValue,同时initializer = null,然后返回结果value

这里涉及到的几个知识点,简单介绍一下:

1.1. 如何查看Kotlin生成的java代码
1.2. 这里的Metadata是干啥的

每个kotlin生成java的类里面都会有这样的一个Metadata的注解

@Metadata(
   ...
)

kotlin官网对此有介绍,Metadata

This annotation is present on any class file produced by the Kotlin compiler and is read by the compiler and reflection. Parameters have very short JVM names on purpose: these names appear in all generated class files, and we'd like to reduce their size.

1.3. lambda表达式

lambda表达式是Kotlin的很重要的一个内容,简单介绍一下上面源码中涉及到的内容,抛砖引玉,有兴趣可以详细了解。
上面的源码中用到了这么一句:

fun <T> lazy(initializer: () -> T)

表明传入了一个lambda表达式,需要无参并返回T。下面列出一些常用的简单lambda函数类型

//无参、无返回值的函数类型(Unit 返回类型不可省略)
() -> Unit
//接收T类型参数、无返回值的函数类型
(T) -> Unit
//接收T类型和A类型参数、无返回值的函数类型(多个参数同理)
(T,A) -> Unit
//接收T类型参数,并且返回R类型值的函数类型
(T) -> R
//接收T类型和A类型参数、并且返回R类型值的函数类型(多个参数同理)
(T,A) -> R

如何得到lambda的结果呢?上面的代码

val typedValue = initializer!!()

使用了()得到执行的结果,也可以使用initializer!!.invoke()得到执行结果。

2. by - 委托

Kotlin 直接支持委托模式,更加优雅,简洁,通过关键字 by 实现委托。kotlin中的委托大致有下面几种形式:

类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

// 定义一个接口
interface Base {
    fun print()
}

// 实现这个接口
class RealImpl(val x: Int) : Base {
    override fun print() {
        print(x)
    }
}

// 实现类委托
class Delegate(b: Base) : Base by b

上面是最简单的类委托的实现:

  1. 定义一个接口Base
  2. 创建一个类RealImpl实现接口
  3. 创建一个类Delegate,使用关键字by,表明Deledate相关的属性方法都委托传入的b来实现

看起来迷迷糊糊的,我们看一下Delegate的java代码实现:

public final class Delegate implements Base {
   // $FF: synthetic field
   private final Base $$delegate_0;

   public Delegate(@NotNull Base b) {
      Intrinsics.checkParameterIsNotNull(b, "b");
      super();
      this.$$delegate_0 = b;
   }

   public void print() {
      this.$$delegate_0.print();
   }
}

一目了然,传入的b赋值给$$delegate_0,Delegate类完全是调用了$$delegate_0的方法来实现自己的方法。

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

val/var <属性名>: <类型> by <表达式>

by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。

import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example {
    var p: String by Delegate()
}

// 委托的类
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 这里委托了 ${property.name} 属性"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef 的 ${property.name} 属性赋值为 $value")
    }
}
fun main(args: Array<String>) {
    val e = Example()
    println(e.p)     // 访问该属性,调用 getValue() 函数

    e.p = "Runoob"   // 调用 setValue() 函数
    println(e.p)
}
//Example@433c675d, 这里委托了 p 属性
//Example@433c675d 的 p 属性赋值为 Runoob
//Example@433c675d, 这里委托了 p 属性

需要注意的是,getValue和setValue的参数是固定的。

标准委托
  1. lazy 上面介绍过了,跳过

  2. NotNull

notNull 适用于那些无法在初始化阶段就确定属性值的场合,如果属性在赋值前就被访问的话则会抛出异常。
用法如下:

class NotNull {
    class User {
        val id: Int by Delegates.notNull<Int>()
    }
}

反编译成java代码:

   public static final class User {
      // $FF: synthetic field
      static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(NotNull.User.class), "id", "getId()I"))};
      @org.jetbrains.annotations.NotNull
      private final ReadWriteProperty id$delegate;

      public final int getId() {
         return ((Number)this.id$delegate.getValue(this, $$delegatedProperties[0])).intValue();
      }

      public User() {
         this.id$delegate = Delegates.INSTANCE.notNull();
      }
   }

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
   private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

源码简单,就是属性委托,在没赋值的时候getValue会抛出异常。

  1. Observable

observable 可以用于实现观察者模式,每次改变都会回调。

    class Time {
        var day: Int by Delegates.observable(0, { property: KProperty<*>, oldValue: Int, newValue: Int ->
            println("change before: $oldValue, after: $newValue")
        })
    }
  1. Vetoable

vetoable与 observable一样,可以观察属性值的变化,不同的是,vetoable可以通过处理器函数来决定属性值是否生效。

    class User {
        var id: Int by Delegates.vetoable(10, { property: KProperty<*>, oldValue: Int, newValue: Int ->
            // only setValue work when newValue bigger than oldValue
            newValue > oldValue
        })
    }

上面的代码表示赋值的时候只有newValueoldValue大的时候才会赋值成功。

源码上ObservableVetoable类似,

    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }


    public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
        }

这两个调用类似,传入初始值和lambda,返回了一个ObservableProperty的匿名内部类,不同的是重写的方法不一样。
observable重写了afterChangevetoable重写了beforeChange
继续看ObservableProperty的源码:

public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
    private var value = initialValue
    protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true
    protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}
    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
}

可以看到其实vetoable就是用beforeChange做了一个写属性的拦截。observable就是做了一个afterChange的回调。

One More Thing. 接口委托

最近看到了一个by的神奇的用法。

interface IInterface {
    fun process()
}

class Impl : IInterface {
    override fun process() {
        System.out.println("process")
    }
}

class Logic(impl: Impl) : IInterface by impl {}

fun main(args: Array<String>) {
    val logic = Logic(Impl())
    logic.process()
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • [toc] 委托是什么 委托又可以称为代理。为其他对象提供一种代理以控制对这个对象的访问,简单的说就是在访问和被访...
    Method阅读 339评论 0 1
  • 本文要点概述 辨析委托模式与代理模式 接口委托(Delegated interface) 属性委托(Delegat...
    JayDroid阅读 414评论 0 1
  • 一. 委托模式 委托模式 是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对...
    wind_sky阅读 547评论 0 1
  • 委托模式[https://www.runoob.com/w3cnote/delegate-mode.html]是软...
    zhongjh阅读 281评论 0 0
  • 在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。 类委托 由输出结果可以看...
    高级复制工程师阅读 343评论 0 0