Kotlin 屬性
要講 Kotlin 的委托屬性坠陈,要先從 Kotlin 的屬性說起婚脱,當然關(guān)于屬性的定義就不多介紹了。這里介紹一下 Kotlin 區(qū)別于 Java 獨有的 back field 的概念喜颁。用過 Kotlin 的人都知道碾牌,Kotlin 的屬性是天生帶 Setter/Getter 方法的,不過如果要重寫他們的話玷犹,寫法有所不同混滔。
var a: String = "1"
get() = field
set(value) {
field = value
}
我們可以看到,當需要重寫 Setter/Getter 方法的時候歹颓,就需要用到 field 這個新概念坯屿,它其實是代表這個域本身。有些人剛開始看到這個東西的時候晴股,可能會覺得很神秘愿伴,其實它里面的實現(xiàn)邏輯很簡單,就是對應到 Java 中 Setter/Getter 方法电湘,然后 field 在 Java 的方法中就是該屬性本身隔节,上面的代碼編譯后的代碼:
@NotNull
private String a = "1";
@NotNull
public final String getA() {
return this.a;
}
public final void setA(@NotNull String value) {
Intrinsics.checkParameterIsNotNull(value, "value");
this.a = value;
}
基于這樣的邏輯,對于 Kotlin
屬性的 lateinit
修飾符的實現(xiàn)原理寂呛,就可以很簡單的推理出來怎诫,在屬性的 Getter
方法中先判斷該屬性是否被賦值,否則的話拋出異常贷痪,下面就是一個用 lateinit
修飾的屬性生成的 Getter
方法幻妓。
@NotNull
public final String getPropLateInit() {
String var10000 = this.propLateInit;
if(this.propLateInit == null) {
Intrinsics.throwUninitializedPropertyAccessException("propLateInit");
}
return var10000;
}
講到這里,反應快的人應該能猜到到劫拢,下面要講的屬性委托是基于什么原理實現(xiàn)的了肉津。
Kotlin 委托屬性
委托屬性的聲明
定義一個委托屬性的語法是 val/var <property name>: <Type> by <expression>
强胰,其中 by
后面的就是屬性的委托。屬性委托不用繼承什么特別的接口妹沙,只要擁有用 operator
修飾的 getValue()
和 setValue()
(適用 var)的函數(shù)就可以了偶洋。
需要注意的是在官方的文檔里,要求 getValue()
和 setValue()
兩個函數(shù)提供固定的參數(shù)距糖,就像下面的例子一樣玄窝。但是事實其實并非如此,這里我們先按照官方的說法繼續(xù)悍引,后面再解釋這里的差異恩脂。
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.'")
}
}
對于參數(shù)的描述這里做一個簡單描述:
-
thisRef
,屬性的擁有者趣斤; -
property
俩块,對屬性的描述,是 KProperty<*> 類型或是它的父類唬渗; -
value
典阵,屬性的值。
委托屬性的背后實現(xiàn)
Kotlin 官方在官方標準庫里提供委托屬性的三個常用場景镊逝,作為委托屬性的范例。這里重點分析一下 lazy
的背后的實現(xiàn)原理嫉鲸,然后順帶講一下 Observable
和 storing
的用法撑蒜。
lazy
通過 lazy
我們可以定義一個懶加載的屬性,該屬性的初始化不會再類創(chuàng)建的時候發(fā)生玄渗,而是在第一次用到它的時候賦值座菠。
val propLazy: Int by lazy{1}
我們查看一下編譯后的 bytecode
。
LINENUMBER 4 L1
ALOAD 0
GETSTATIC PropertiesDemo$propLazy$2.INSTANCE : LPropertiesDemo$propLazy$2;
CHECKCAST kotlin/jvm/functions/Function0
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
PUTFIELD PropertiesDemo.propLazy$delegate : Lkotlin/Lazy;
L2
字節(jié)碼的可讀性太差藤树,我們反編譯一下浴滴,找到相關(guān)的代碼。
public PropertiesDemo() {
this.propLazy$delegate = LazyKt.lazy((Function0)null.INSTANCE);
}
@NotNull
private final Lazy propLazy$delegate;
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(PropertiesDemo.class), "propLazy", "getPropLazy()I")))};
public final int getPropLazy() {
Lazy var1 = this.propLazy$delegate;
KProperty var3 = $$delegatedProperties[0];
return ((Number)var1.getValue()).intValue();
}
可以看到 Kotlin 為我們生成了一個 Lazy
類型的 propLazy$delegate
屬性岁钓,同時生成一個 getPropLazy()
方法升略,但是我們并沒有找到 propLazy
屬性的定義(這一點我們先不管,后面再說)屡限。
在 getPropLazy()
的實現(xiàn)里可以看到返回的是 propLazy$delegate.getValue()
的值品嚣,再看下 propLazy$delegate
的賦值是在類的構(gòu)造函數(shù)里面 this.propLazy$delegate = LazyKt.lazy((Function0)null.INSTANCE);
。LazyKt 是系統(tǒng)的 Lazy.kt
文件生成的類文件钧大,找到 Lazy.kt
的 lazy()
方法翰撑,返回的是 SynchronizedLazyImpl
的實例。
@kotlin.jvm.JvmVersion
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
在 SynchronizedLazyImpl
實現(xiàn)代碼里啊央,通過 _value
用來真正保存屬性的值眶诈。_value
的默認值是 UNINITIALIZED_VALUE
(一個自定義的對象)涨醋。當 _value
不是默認值的時候,就會直接把 _value
的值作為 getValue()
的返回逝撬;當 _value
還是默認值的時候东帅,就會調(diào)用 initializer
初始化表達式完成初始化,賦值給 _value
并作為 getValue()
的返回球拦。
private object UNINITIALIZED_VALUE
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)
}
我們發(fā)現(xiàn) SynchronizedLazyImpl
的 getValue()
方法并沒有帶參數(shù)靠闭,在反編譯的 getPropLazy()
代碼中 KProperty var3 = $$delegatedProperties[0];
這個變量其實根本沒有用到,其實在正常的委托的反編譯的代碼是類似這樣的坎炼。
return (String)this.propObservable$delegate.getValue(this, $$delegatedProperties[1]);
所以說其實我們在定義委托的時候愧膀,getValue()
和 setValue()
方法是可以不帶參數(shù)的,只是官方在編譯階段做了限制谣光,導致我們只能擁有帶參數(shù)的方法檩淋。為了驗證如果這個想法,我參考 lazy
實現(xiàn)了一個類似的功能萄金,發(fā)現(xiàn)根本不能通過編譯蟀悦。
關(guān)于 propLazy
屬性本身
前面我們有提到在生成的字節(jié)碼中,并不能找到 propLazy
這個屬性的定義氧敢,我們先看看官網(wǎng)怎么說的日戈。
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
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)
}
根據(jù)官方的文檔描述,Kotlin
會自動生成 prop$delegate
屬性孙乖,并復寫 prop
的 Setter/Getter
方法浙炼。按照這個說話的話,我們上面在編譯后的字節(jié)碼里面應該是可以找到 propLazy
屬性的唯袄。
為了驗證這個問題弯屈,我首先想到是不是因為這個屬性是私有變量,在類里面沒有使用恋拷,所以 Kotlin
編譯器為了優(yōu)化生成字節(jié)碼的數(shù)量而故意去掉了呢资厉。于是我故意在另外一個方法里嘗試輸出該屬性,但是最后發(fā)現(xiàn)在編譯后該處的使用被替換成 getPropLazy()
方法的調(diào)用蔬顾,所以看來 propLazy
是真的沒有了宴偿。
為了進一步驗證這個想法,我們還在運行時用反射的方法去獲取該屬性阎抒,發(fā)現(xiàn)的確找不到該屬性酪我,最后我們得出結(jié)論是委托屬性在編譯后會生成對應的 prop$delegate
(被委托的屬性」),然后生成生成委托屬性的 Setter/Getter
方法且叁,但是該屬性本身并不在類的域定義里面都哭,這個時候嘗試用反射的方法直接拿到這個屬性是做不到的(當然你可以通過 prop$delegate
反射到你想要的內(nèi)容)。
Observable
官方推薦另外一個委托屬性的應用就是 Observable
,讓屬性在發(fā)生變動的時候可以被關(guān)注的地方觀察到欺矫。
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
上面代碼的輸出:
<no name> -> first
first -> second
想了解 Observable
的實現(xiàn)方式纱新,大家可以參考前面分析 lazy
的方法,去探究一下穆趴。關(guān)于 Observable
的進一步實現(xiàn)場景脸爱,我們一直有一個想法,就是基于這個特性封裝出一套 MVVM 的框架未妹,等到這個框架實現(xiàn)以后簿废,再和大家分享。
Storing
Storing
的使用場景是被模型的屬性全部委托到 Map
的結(jié)構(gòu)去真實的存儲數(shù)據(jù)络它,用于解析 Json
或者做一些動態(tài)的事情族檬。
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
不過根據(jù)我的了解,一些 Json 的解析庫是直接用反射的方式實現(xiàn)的反序列化化戳,根據(jù)我們前面的分析单料,這里根本解析不出來,所以這個場景看來是使用不了了点楼。
關(guān)于 BufferKnife
和 KotterKnife
BufferKnife
在 Kotlin
剛推出來的時候扫尖,由于不支持 apt
,所以會導致 BufferKnife
這類用注解實現(xiàn)的框架會使用不了掠廓,但是 Kotlin
很快就意識到這個問題并推出 kapt
换怖。在 kapt
推出來以后其實 BufferKnife
就可以正常使用了,我們也在我們的代碼里使用了 BufferKnife
却盘。但當時 BufferKnife
在增量編譯的時候有時候的確會出一些問題狰域,導致我們那個時候最后選擇了放棄,我們自己簡單封裝下 findViewById
的操作黄橘,有興趣的可以看下 AndroidExtension ∏龋可能有些人關(guān)于 Kotlin
和 BufferKnife
的沖突信息是來自我們當時不準確的描述塞关,導致認為他們不能一起使用。而且經(jīng)過這么久的迭代子巾,我相信官方應該早就解決這個問題了帆赢。
KotterKnife
KotterKnife
這個庫的存在可能也是很多人認為 Kotlin
不能使用 BufferKnife
的一個因素。在我看來 KotterKnife
創(chuàng)建的時機是 Kotlin
還不支持 apt
的時候线梗,在 Kotlin
推出 kapt
以后這個庫就已經(jīng)不怎么更新了椰于,而且這個庫從來沒有發(fā)布過一個正式版本,所以可以看出這只是大神在用 Kotlin
做的一些新的嘗試而已(這一點我是通過查看代碼發(fā)現(xiàn) KotterKnife
主要使用「委托屬性」這個特性猜想出來的仪搔,僅供參考)瘾婿。