Kotlin-属性委托深入详解
学习一下属性的委托(delegated property),我们知道定义一个类的属性是需要给它一个初始值的,如果不给会报错,如下
class MyPropertyClass {
var str: String
}
Property must be initialized or be abstract
可以加一个延迟属性来避免:
class MyPropertyClass { lateinit var str: String }
当然咱们不用这种方式,而是可以将此属性的赋值进行委托,目前该属性是可读可写,则在委托属性中需要定义可读可写的,而如果是用的val定义的属性则只需要定义可读的委托属性,具体如何做呢?下面先来定义一个委托类:
class MyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String =
"your delegated property name is ${property.name}"
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) =
println("new value is $value")
}
注意:该方法的名称是有严格要求的,不能乱写方法名
operator fun getValue(thisRef: Any?, property: KProperty<*>): String
定义参数,第一个参数是要给哪个类进行委托,第二个参数是其委托的属性是哪一个,
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String)
再来定义setValue()方法,此时因为需要设置值,所以多了一个参数,其函数的参数大体跟getValue()差不多
class MyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String =
"your delegated property name is ${property.name}"
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) =
println("new value is $value")
}
class MyPropertyClass {
var str: String by MyDelegate()
}
fun main(args: Array<String>) {
val myPropertyClass = MyPropertyClass()
myPropertyClass.str = "hello world"
println(myPropertyClass.str)
}
RUN> 🏂🏂🏂🏂🏂🏂
new value is hello world your delegated property name is str Process finished with exit code 0
从打印来看其值的读写确实是被委托了
其中这里使用了运算符重载,貌似C++中也有这个关键字,如下:
那对于委托属性在实际开发中是有如下4种使用情况的
1、延迟属性。
2、可观测属性。
3、非空属性。
4、map属性。
延迟属性:
先来看啥叫延迟属性:它指的是属性只有第一次被访问的时候才会计算,之后则会将之前的计算结果缓存起来供后续调用。下面看个具体例子:
val myLazyValue: Int by lazy(LazyThreadSafetyMode.NONE) {
println("hello world")
30
}
fun main(args: Array<String>) {
println(myLazyValue)
println("----")
println(myLazyValue)
}
RUN> 🏄🏄🏄🏄🏊🏊🏊🏊🏊
hello world 30 ---- 30 Process finished with exit code 0
其中lazy是个函数,咱们来看一下它的声明:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
另外lazy函数还有另外一个重载的版本,如下:
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
LazyThreadSafetyMode 多了一个模式参数
LazyThreadSafetyMode
- SYNCHRONIZED: 默认情况下,延迟属性的计算是同步的:值只会在一个线程中得到计算,所有线程都会使用相同的一个结果。
- PUBLICATION: 如果不需要初始化委托的同步,这样多个线程可以同时执行。
- NONE:如果确定初始化操作只会在一个线程中执行,这样会减少线程安全方面的开销
非空属性:
我们知道对于定义的属性是必须要给它赋初值的,不然就报错了,但是如果没有一个合适的初值能赋给它,此时就可以将它委托给非空属性,如下:
// 非空属性
// notNull适用于那些无法在初始化阶段就确定属性值的场合
class MyPerson {
var address: String by Delegates.notNull<String>()
}
其中看一下Delegated:
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
}
}
它实现了ReadWriteProperty这个接口,如下:
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
/**
* Sets the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @param value the value to set.
*/
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
也就是系统对于委托的这俩函数已经定义好了,关于这个接口在未来还会学习的。
fun main() {
var myPerson = MyPerson()
myPerson.address = "Hangzhou"
println(myPerson.address)
}
RUN> 🏄🏄🏄🏂🏂🏂
Hangzhou Process finished with exit code 0
如果不赋值就直接读取呢?
如javadoc所说。所以notNull通常适用于那些无法在初始化阶段就确定属性值的场合。