美文网首页
Kotlin的by 委托

Kotlin的by 委托

作者: YocnZhao | 来源:发表于2022-01-11 19:14 被阅读0次

    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的回调。

    相关文章

      网友评论

          本文标题:Kotlin的by 委托

          本文链接:https://www.haomeiwen.com/subject/spticrtx.html