文章目录
在 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 方案了,运行了一下,非常完美。
下面贴一下代码,写的非常优秀。
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
在写委托的时候,要写 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) {
}
}
延迟委托是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
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) {
("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
("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)
}