Kotlin -by 详解

Posted wzgiceman

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin -by 详解相关的知识,希望对你有一定的参考价值。

Kotlin 中 by 关键字用来简化实现代理 (委托) 模式,不仅可以类代理,还可以代理类属性, 监听属性变化,下面我们来介绍by的几种主要使用场景:

  • 类的代理 class
  • 属性延迟加载 lazy
  • 可观察属性 Delegates.observable ( 扩展 Delegates.vetoable )
  • 自定义监听属性变化 ReadWriteProperty
  • 属性非空强校验 Delegates.notNull()
  • Map值 映射到类属性 map

类的代理(代理/委托模式)

class ByTest 

    // 定义一个接口,和一个方法 show()
    interface Base 
        fun show()
    

    // 定义类实现 Base 接口, 并实现 show 方法
    open class BaseImpl : Base 
        override fun show() 
            AbLogUtil.e("BaseImpl::show()")
        
    

    // 定义代理类实现 Base 接口, 构造函数参数是一个 Base 对象
    // by 后跟 Base 对象, 不需要再实现 show()
    class BaseProxy(base: Base) : Base by base 
        fun showOther() 
            AbLogUtil.e("BaseImpl::showOther()")
        

    

    // main 方法
    fun mainGo() 
        val base = BaseImpl()
        BaseProxy(base).show()
        BaseProxy(base).showOther()
    

输出结果

 BaseImpl::show()
 BaseImpl::showOther()

转成 Java 代码

public interface Base   
   void show();



// BaseImpl.java

public class BaseImpl implements Base   
   public void show() 
      String var1 = "BaseImpl::show()";
      System.out.print(var1);
   



// BaseProxy.java

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

   public BaseProxy(@NotNull Base base) 
      Intrinsics.checkParameterIsNotNull(base, "base");
      super();
      this.$$delegate_0 = base;
   

   public void show() 
      this.$$delegate_0.show();
   

// NormalKt.java

public final class NormalKt   
   public static final void main(@NotNull String[] args) 
      Intrinsics.checkParameterIsNotNull(args, "args");
      BaseImpl base = new BaseImpl();
      (new BaseProxy((Base)base)).show();
   

和一般的代理模式是不是一样?不过 by 关键字节省了不少代码

属性延迟加载

可以理解成按需加载,使用时生成一个类单例对象,不使用无须生成对象

 private val user: User by lazy  ShareSparse.getValueBy(ShareSparse.USER_CLS) as User 
 private lateinit var sex: String

lzay 后跟表达式,表达式返回值必须和属性类型一致

注意 lateinit修饰的对象在赋值前调用会抛出异常

可观察属性

可观察属性,本质就是观察者模式,在Java中也可以实现这个设计模式,但Kotlin实现观察者模式不需要样板代码。在谈Kotlin的可观察属性前,先看下Kotlin里面的委托。同样的,委托也是一种设计模式,它的结构如下图所示:

Kotlin通过 Delegates.observable()实现可观察属性:

 // main 方法
    fun mainGo() 
        name = "dddd"
        name = "hhhh"
    


//    观察属性

    var name: String by Delegates.observable("hello",  kProperty: KProperty<*>, oldName: String, newName: String ->
        AbLogUtil.e("$kProperty.name---$oldName--$newName")
    )


输出

    name---hello--dddd
    name---dddd--hhhh

可观察属性有什么用处呢?ListView中有一个经典的Crash:在数据长度与Adapter中的Cell的长度不一致时,会报IllegalStateException异常。这个异常的根本原因是修改了数据之后,没有调用notifyDataSetChanged,导致ListView没有及时刷新。如果我们把数据做成可观察属性,在观察回调方法中直接刷新ListView,可以杜绝这个问题;其实很多简单的接口回调处理也可以使用

源码解析

// observable方法
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)
    

// vetoable 方法
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)
    


public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T>   
    private var value = initialValue

    /**
     *  The callback which is called before a change to the property value is attempted.
     *  The value of the property hasn't been changed yet, when this callback is invoked.
     *  If the callback returns `true` the value of the property is being set to the new value,
     *  and if the callback returns `false` the new value is discarded and the property remains its old value.
     */
    protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true

    /**
     * The callback which is called after the change of the property is made. The value of the property
     * has already been changed when this callback is invoked.
     */
    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)
    

属性非空强校验

class User   
    var name: String by Delegates.notNull()

    fun init(name: String) 
        this.name = name
    


fun main(args: Array<String>)   
    val user = User()
    // print(user.name)
    // user.name -> IllegalStateException
    user.init("Carl")
    println(user.name)

源码解析

// notNull 方法
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()

// 具体实现方法 NotNullVar()
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
    

自定义监听属性变化

常用的以上by lazy 延迟委托 , by Delegates.observable() 可观察属性委托, by Delegates.nonNull()等等

语法是: val/var<属性名>:<类型> by<表达式>。在 by后面的表达式是该委托

属性对应的get() (和set())会被委托给它的getValue()和setValue()方法。
所以,kotlin中的代理仅仅是代理了get 和 set 两个方法

属性的委托不必实现任何的接口,但是需要重载操作符getValue()函数(和setValue()——对于var属性)但是对于val可以实现ReadOnlyProperty,var实现ReadWriteProperty接口(就是帮你实现两个需要重载的操作符的接口)来更快的进行自定义委托.

那么他们究竟是怎么工作的呢?
我们来看看nonNull()委托的代码吧
我初始化了一个Int对象,且用委托表示int1不可以为空
var int1 : Int by Delegates.notNull()
那么我们进入notNull()的代码里查看他的实现
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
很明显,代理给一个ReadWriteProperty类了
那么NotNullVar()是什么呢

// notNull 方法
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()

// 具体实现方法 NotNullVar()
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
    

解释一下,当给int1复制的时候,就会自动调用上面的setValue(),其中的三个参数
第一个thisRef表示持有该对象的对象,即该int1的所有者.
第二个参数 property 是该值的类型,
第三个参数 value 就是属性的值了
在第一次调用setvalue的时候,将该值存到value中,getvalue的时候对value进行是否为空的判断.空就抛异常,这就完成了nonNull的委托

然后实现一个自定义的委托属性

//自定义委托属性
    class A() 
        // 运算符重载
        operator fun getValue(thisRef: Any?, prop: KProperty<*>): String 
            return "$thisRef, thank you for delegating '$prop.name' to me!"
        

        operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) 
            println("$value has been assigned to $prop.name in $thisRef")
        
    

    // main 方法
    fun mainGo() 
        var name :String by A()
        name="aaaa"
        AbLogUtil.e(name)
    

输出

 aaaa has been assigned to name in null
 null, thank you for delegating 'name' to me!

比较实用可以方便实现弱引用对象处理,防止移动端内存泄露

/**
 *
 *Describe:弱引用封装类 -kotlin
 *
 *Created by zhigang wei
 *on 2018/5/4
 *
 *Company :cpx
 */
class Weak<T : Any>(initializer: () -> T?) 
    var weakReference = WeakReference<T?>(initializer())

    constructor() : this(
        null
    )

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? 
        return weakReference.get()
    

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) 
        weakReference = WeakReference(value)
    



Map值 映射到类属性

class UserX(val map: Map<String, Any?>)   
    val name: String by map
    val age: Int     by map


fun main(args: Array<String>)   
    val user = UserX(mapOf(
            "name" to "John Doe",
            "age"  to 123
    ))

    // key 不存在报错  Key age is missing in the map.
    // 类型不一致 java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

    println("name = $user.name, age = $user.age")

以上是关于Kotlin -by 详解的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin——数组

Kotlin中接口抽象类泛型out(协变)in(逆变)reified关键字的详解

Kotlin中与Java互操作与可空性类型映射属性访问@JvmOverloads@JvmField@JvmStatic@Throws和函数类型操作详解

Kotlin -特殊用法详解

Kotlin -特殊用法详解

Kotlin——高级篇:高阶函数详解与标准的高阶函数使用