Kotlin-屬性委托深入詳解
學(xué)習(xí)一下屬性的委托(delegated property),我們知道定義一個(gè)類的屬性是需要給它一個(gè)初始值的向叉,如果不給會(huì)報(bào)錯(cuò),如下
class MyPropertyClass {
var str: String
}
Property must be initialized or be abstract
image-20210622165200676可以加一個(gè)延遲屬性來避免:
class MyPropertyClass { lateinit var str: String }
當(dāng)然咱們不用這種方式嗦董,而是可以將此屬性的賦值進(jìn)行委托母谎,目前該屬性是可讀可寫,則在委托屬性中需要定義可讀可寫的展懈,而如果是用的val定義的屬性則只需要定義可讀的委托屬性销睁,具體如何做呢?下面先來定義一個(gè)委托類:
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")
}
注意:該方法的名稱是有嚴(yán)格要求的存崖,不能亂寫方法名
operator fun getValue(thisRef: Any?, property: KProperty<*>): String
定義參數(shù)冻记,第一個(gè)參數(shù)是要給哪個(gè)類進(jìn)行委托,第二個(gè)參數(shù)是其委托的屬性是哪一個(gè)来惧,
image-20210622165630562operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String)
再來定義setValue()方法冗栗,此時(shí)因?yàn)樾枰O(shè)置值,所以多了一個(gè)參數(shù),其函數(shù)的參數(shù)大體跟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
從打印來看其值的讀寫確實(shí)是被委托了
其中這里使用了運(yùn)算符重載隅居,貌似C++中也有這個(gè)關(guān)鍵字钠至,如下:
那對(duì)于委托屬性在實(shí)際開發(fā)中是有如下4種使用情況的
1、延遲屬性胎源。
2棉钧、可觀測屬性。
3涕蚤、非空屬性宪卿。
4、map屬性万栅。
延遲屬性:
先來看啥叫延遲屬性:它指的是屬性只有第一次被訪問的時(shí)候才會(huì)計(jì)算佑钾,之后則會(huì)將之前的計(jì)算結(jié)果緩存起來供后續(xù)調(diào)用。下面看個(gè)具體例子:
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
image-20210622170411925
其中l(wèi)azy是個(gè)函數(shù)烦粒,咱們來看一下它的聲明:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
另外lazy函數(shù)還有另外一個(gè)重載的版本休溶,如下:
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 多了一個(gè)模式參數(shù)
image-20210622170730382LazyThreadSafetyMode
- SYNCHRONIZED: 默認(rèn)情況下,延遲屬性的計(jì)算是同步的:值只會(huì)在一個(gè)線程中得到計(jì)算扰她,所有線程都會(huì)使用相同的一個(gè)結(jié)果兽掰。
- PUBLICATION: 如果不需要初始化委托的同步,這樣多個(gè)線程可以同時(shí)執(zhí)行徒役。
- NONE:如果確定初始化操作只會(huì)在一個(gè)線程中執(zhí)行禾进,這樣會(huì)減少線程安全方面的開銷
非空屬性:
我們知道對(duì)于定義的屬性是必須要給它賦初值的,不然就報(bào)錯(cuò)了廉涕,但是如果沒有一個(gè)合適的初值能賦給它,此時(shí)就可以將它委托給非空屬性艇拍,如下:
// 非空屬性
// 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
}
}
image-20210622171211191
它實(shí)現(xiàn)了ReadWriteProperty這個(gè)接口狐蜕,如下:
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)
}
也就是系統(tǒng)對(duì)于委托的這倆函數(shù)已經(jīng)定義好了,關(guān)于這個(gè)接口在未來還會(huì)學(xué)習(xí)的卸夕。
fun main() {
var myPerson = MyPerson()
myPerson.address = "Hangzhou"
println(myPerson.address)
}
RUN> ????????????
Hangzhou Process finished with exit code 0
如果不賦值就直接讀取呢?
image-20210622171555643如javadoc所說层释。所以notNull通常適用于那些無法在初始化階段就確定屬性值的場合。