Kotlin by属性委托

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119939781
本文出自【赵彦军的博客】

在 Kotlin 中,通过 by 实现属性委托,属性委托 是什么意思呢?
简单来说,就是属性的 set、get 的操作,交给另一个对象器完成。

举个例子:

class Example {
    var p: String by Delegate()
}

语法是: val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该 委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(与 setValue()——对于 var 属性)。 例如:

mport kotlin.reflect.KProperty

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.")
    }
}

当我们从委托到一个 Delegate 实例的 p 读取时,将调用 Delegate 中的 getValue() 函数, 所以它第一个参数是读出 p 的对象、第二个参数保存了对 p 自身的描述 (例如你可以取它的名字)。 例如:

val e = Example()
println(e.p)

输出结果:

Example@33a17727, thank you for delegating ‘p’ to me!

类似地,当我们给 p 赋值时,将调用 setValue() 函数。前两个参数相同,第三个参数保存将要被赋予的值:

e.p = "NEW"

输出结果:

NEW has been assigned to ‘p’ in Example@33a17727.

属性委托要求

对于一个只读属性(即 val 声明的),委托必须提供一个操作符函数 getValue(),该函数具有以下参数:

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。
  • property —— 必须是类型 KProperty<*> 或其超类型。

getValue() 必须返回与属性相同的类型(或其子类型)。

class Resource

class Owner {
    val valResource: Resource by ResourceDelegate()
}

class ResourceDelegate {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
        return Resource()
    }
}

对于一个可变属性(即 var 声明的),委托必须额外提供一个操作符函数 setValue(), 该函数具有以下参数:

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。
  • property —— 必须是类型 KProperty<*> 或其超类型。
  • value — 必须与属性类型相同(或者是其超类型)。
class Resource

class Owner {
    var varResource: Resource by ResourceDelegate()
}

class ResourceDelegate(private var resource: Resource = Resource()) {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
        return resource
    }
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) {
        if (value is Resource) {
            resource = value
        }
    }
}

getValue() 或/与 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要用 operator 关键字来进行标记。

委托原理

在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

class C {
    var prop: Type by MyDelegate()
}

// 这段是由编译器生成的相应代码:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

简单来说,委托之所以能实现,是因为kotlin 在编译期间帮我们写了代码,动态的做了属性的 set / get 方法

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

实战演练,SharedPreference 委托

创建 UtilSharedPreference 委托类

/**
 * @author : zhaoyanjun
 * @time : 2021/8/25
 * @desc :
 */
class UtilSharedPreference<T>(private val key: String, private val default: T) {

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Any) {
        sp.edit().apply {
            when (value) {
                is Long -> putLong(key, value)
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
                is Float -> putFloat(key, value)
                is Set<*> -> putStringSet(key, value as Set<String>) // only support Set<String>
                else -> throw IllegalArgumentException("SharedPreferences can't be save this type")
            }.apply()
        }
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        sp.apply {
            val res: Any = when (default) {
                is Long -> getLong(key, default)
                is String -> getString(key, default) ?: ""
                is Int -> getInt(key, default)
                is Boolean -> getBoolean(key, default)
                is Float -> getFloat(key, default)
                is Set<*> -> getStringSet(key, default as Set<String>) ?: default as Set<String>
                else -> throw IllegalArgumentException("SharedPreferences can't be get this type")
            }
            return res as T
        }
    }

    companion object {
        lateinit var sp: SharedPreferences

        fun initSharedPreference(context: Context, fileName: String) {
            sp = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
        }
    }
}

使用如下:

class MainActivity : AppCompatActivity() {
    //委托
    var name: String by UtilSharedPreference("name", "")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //初始化
        UtilSharedPreference.initSharedPreference(this, "sp_file")

        //取值
        val newName = name
        //赋值
        name = "ppp"

    }
}

到这里,我们已经实现了一个 SharedPreferences 方案了,运行了一下,非常完美。

在我查阅资料的时候,发现了一个大神的实现方式,地址是:

https://wazing.github.io/2019/05/23/kotlin-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A7%94%E6%89%98%E6%96%B9%E5%BC%8F%E5%AE%9E%E7%8E%B0SharedPreferences/

下面贴一下代码,写的非常优秀。

object SharedPreferencesUtils {

    object User : Delegates() {

        override fun getSharedPreferencesName(): String = this.javaClass.simpleName

        var name by string()

        var phone by long()
    }

    abstract class Delegates {

        private val preferences: SharedPreferences by lazy {
            BaseApplication.instance.applicationContext.getSharedPreferences(
                getSharedPreferencesName(),
                Context.MODE_PRIVATE
            )
        }

        fun int(defaultValue: Int = 0) = object : ReadWriteProperty<Any, Int> {
            override fun getValue(thisRef: Any, property: KProperty<*>): Int {
                return preferences.getInt(property.name, defaultValue)
            }

            override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
                preferences.edit().putInt(property.name, value).apply()
            }
        }

        fun string(defaultValue: String? = null) = object : ReadWriteProperty<Any, String?> {
            override fun getValue(thisRef: Any, property: KProperty<*>): String? {
                return preferences.getString(property.name, defaultValue)
            }

            override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) {
                preferences.edit().putString(property.name, value).apply()
            }
        }

        fun long(defaultValue: Long = 0L) = object : ReadWriteProperty<Any, Long> {

            override fun getValue(thisRef: Any, property: KProperty<*>): Long {
                return preferences.getLong(property.name, defaultValue)
            }

            override fun setValue(thisRef: Any, property: KProperty<*>, value: Long) {
                preferences.edit().putLong(property.name, value).apply()
            }
        }

        fun boolean(defaultValue: Boolean = false) = object : ReadWriteProperty<Any, Boolean> {
            override fun getValue(thisRef: Any, property: KProperty<*>): Boolean {
                return preferences.getBoolean(property.name, defaultValue)
            }

            override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) {
                preferences.edit().putBoolean(property.name, value).apply()
            }
        }

        fun float(defaultValue: Float = 0.0f) = object : ReadWriteProperty<Any, Float> {
            override fun getValue(thisRef: Any, property: KProperty<*>): Float {
                return preferences.getFloat(property.name, defaultValue)
            }

            override fun setValue(thisRef: Any, property: KProperty<*>, value: Float) {
                preferences.edit().putFloat(property.name, value).apply()
            }
        }

        fun setString(defaultValue: Set<String>? = null) = object :
            ReadWriteProperty<SharedPreferencesUtils, Set<String>?> {
            override fun getValue(thisRef: SharedPreferencesUtils, property: KProperty<*>): Set<String>? {
                return preferences.getStringSet(property.name, defaultValue)
            }

            override fun setValue(thisRef: SharedPreferencesUtils, property: KProperty<*>, value: Set<String>?) {
                preferences.edit().putStringSet(property.name, value).apply()
            }
        }

        fun clearAll() {
            preferences.edit().clear().apply()
        }

        abstract fun getSharedPreferencesName(): String
    }
}

使用方式

// 该方式会存储到SP中
SharedPreferencesUtils.User.name = "张无忌"
SharedPreferencesUtils.User.phone = 18812345678
// 读取
val name = SharedPreferencesUtils.User.name

升级之旅 ReadWriteProperty

在写委托的时候,要写 getValue 、setValue 方法,也是有点麻烦,好在系统已经内置了接口。

自定义的委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口之一。 这俩接口是在 Kotlin 标准库中声明的:

ublic 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)
}

如果我们要实现自己的委托就可以直接实现 ReadOnlyProperty 、ReadWriteProperty 接口就行了。

举例如下:

/**
 * @author : zhaoyanjun
 * @time : 2021/8/25
 * @desc : 自定义代理类
 */
class MyUtil : ReadWriteProperty<Any, String> {

    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return ""
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {

    }
}

延迟委托 Lazy

延迟委托是kotlin中最为常用的,lazy()后面接受lambda并返回一个lazy实例,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

private val str: String by lazy {
      println("(=・ω・=)")
      "hello world"
}

// 多次输出 str 变量,只会输出一次(=・ω・=),多次 hello world

原理如下

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)
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值