第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

首頁 慕課教程 Kotlin 教程 Kotlin 教程 Kotlin 委托(屬性代理)

Kotlin委托(屬性代理)

這篇文章一起來看下 Kotlin 委托屬性代理。屬性代理可以說是 Kotlin 獨(dú)有的強(qiáng)大的功能之一,特別是對(duì)于框架開發(fā)的小伙伴來說非常有用,因?yàn)闀?huì)經(jīng)常涉及到更改存儲(chǔ)和修改屬性的方式操作,例如 Kotlin 中的 SQL 框架 Exposed 源碼就大量使用了屬性代理。相信你已經(jīng)在代碼也使用了諸如Delegates.observable()、Delegates.notNull()、Delegates.vetoable()或者自定義的屬性代理。

1. 屬性代理的基本定義

1.1 基本定義

屬性代理是借助于代理設(shè)計(jì)模式,把這個(gè)模式應(yīng)用于一個(gè)屬性時(shí),它可以將訪問器的邏輯代理給一個(gè)輔助對(duì)象。

可以簡(jiǎn)單理解為屬性的 setter、getter 訪問器內(nèi)部實(shí)現(xiàn)是交給一個(gè)代理對(duì)象來實(shí)現(xiàn),相當(dāng)于使用一個(gè)代理對(duì)象來替換了原來簡(jiǎn)單屬性字段讀寫過程,而暴露外部屬性操作還是不變的,照樣是屬性賦值和讀取,只是 setter、getter 內(nèi)部具體實(shí)現(xiàn)變了。

1.2 基本語法格式

class Student{
    var name: String by Delegate()
}

class Delegate{
    operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T{
        ...
    }
    operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T){
        ...
    }
}

屬性 name 將它訪問器的邏輯委托給了 Delegate 對(duì)象,通過 by 關(guān)鍵字對(duì)表達(dá)式 Delegate() 求值獲取這個(gè)對(duì)象。任何符合屬性代理規(guī)則都可以使用 by 關(guān)鍵字。屬性代理類必須要遵循 getValue(),setValue()方法約定,getValue、setValue方法可以是普通方法也可以是擴(kuò)展方法,并且是方法是支持運(yùn)算符重載。如果是 val 修飾的屬性只需要具備 getValue() 方法即可。

屬性代理基本流程就是代理類中的 getValue() 方法包含屬性getter訪問器的邏輯實(shí)現(xiàn),setValue()方法包含了屬性setter訪問器的邏輯實(shí)現(xiàn)。當(dāng)屬性 name 執(zhí)行賦值操作時(shí),會(huì)觸發(fā)屬性 setter 訪問器,然后在setter 訪問器內(nèi)部調(diào)用 delegate 對(duì)象的 setValue() 方法;執(zhí)行讀取屬性 name 操作時(shí),會(huì)在 getter 訪問器中調(diào)用 delegate 對(duì)象的 getValue 方法.

1.3 by 關(guān)鍵字

by 關(guān)鍵字實(shí)際上就是一個(gè)屬性代理運(yùn)算符重載的符號(hào),任何一個(gè)具備屬性代理規(guī)則的類,都可以使用by 關(guān)鍵字對(duì)屬性進(jìn)行代理。

2. 常見屬性代理基本使用

屬性代理是 Kotlin 獨(dú)有的特性,我們自己去自定義屬性代理,當(dāng)然 Kotlin 還提供了幾種常見的屬性代理實(shí)現(xiàn)。例如:Delegates.notNull(), Delegates.observable(), Delegates.vetoable()。

2.1 Delegates.notNull() 的基本使用

Delegate.notNull() 代理主要用于可以不在構(gòu)造器初始化時(shí)候初始化而是可以延遲到之后再初始化這個(gè)var 修飾的屬性,它和 lateinit 功能類似,但是也有一些不同,不過它們都需要注意的一點(diǎn)是屬性的生命周期,開發(fā)者要做到可控,也就是一定要確保屬性初始化是在屬性使用之前,否則會(huì)拋出一個(gè)IllegalStateException。

package com.imooc.kotlin.delegate

import kotlin.properties.Delegates

class Teacher {
    var name: String by Delegates.notNull()
}
fun main(args: Array<String>) {
    val teacher = Teacher().apply { name = "Mikyou" }
    println(teacher.name)
}

可能有的人并沒有看到 notNull() 有什么大的用處

在 Kotlin 開發(fā)中與 Java 不同的是在定義和聲明屬性時(shí)必須要做好初始化工作,否則編譯器會(huì)提示報(bào)錯(cuò)的,不像 Java 只要定義就 OK 了,管你是否初始化呢。

我解釋下這也是 Kotlin 優(yōu)于 Java 地方之一,沒錯(cuò)就是空類型安全,就是 Kotlin 在寫代碼時(shí)就讓你明確一個(gè)屬性是否初始化,不至于把這樣的不明確定義拋到后面運(yùn)行時(shí)。如果在 Java 你忘記了初始化,那么恭喜你在運(yùn)行時(shí)你就會(huì)拿到空指針異常。

相比 Java,Kotlin 屬性定義時(shí)多出了額外的屬性初始化的工作。但是可能某個(gè)屬性的值在開始定義的時(shí)候你并不知道,而是需要執(zhí)行到后面的邏輯才能拿到。這時(shí)候解決方式大概有這么幾種:

  • 方式A:開始初始化的時(shí)給屬性賦值個(gè)默認(rèn)值

  • 方式B:使用Delegates.notNull()屬性代理;

  • 方式C:使用lateinit修飾屬性

以上三種方式有局限性,方式 A 就是很直接賦默認(rèn)值,對(duì)于基本類型還可以,但是對(duì)于引用類型的屬性,賦值一個(gè)默認(rèn)引用類型對(duì)象就感覺不太合適了。方式 B 適用于基本數(shù)據(jù)類型和引用類型,但是存在屬性初始化必須在屬性使用之前為前提條件。方式 C 僅僅適用于引用類型,但是也存在屬性初始化必須在屬性使用之前為前提條件。

優(yōu)缺點(diǎn)分析:

屬性使用方式 優(yōu)點(diǎn) 缺點(diǎn)
方式A(初始化賦默認(rèn)值) 使用簡(jiǎn)單,不存在屬性初始化必須在屬性使用之前的問題 僅僅適用于基本數(shù)據(jù)類型
方式B(Delegates.notNull()屬性代理) 適用于基本數(shù)據(jù)類型和引用類型
2、不支持外部注入工具將它直接注入到Java字段中
方式C(lateinit修飾屬性) 僅適用于引用類型 1、存在屬性初始化必須在屬性使用之前的問題;
2、不支持基本數(shù)據(jù)類型

Tips: 如果能對(duì)屬性生命周期做很好把控的話,且不存在注入到外部字段需求,建議使用方式 B;此外還有一個(gè)不錯(cuò)建議就是方式 A+ 方式 C組合,或者方式 A+ 方式 B 組合。具體看實(shí)際場(chǎng)景需求。

2.2 Delegates.observable()的基本使用

Delegates.observable()主要用于監(jiān)控屬性值發(fā)生變更,類似于一個(gè)觀察者。當(dāng)屬性值被修改后會(huì)往外部拋出一個(gè)變更的回調(diào)。它需要傳入兩個(gè)參數(shù),一個(gè)是 initValue 初始化的值,另一個(gè)就是回調(diào) lamba, 回調(diào)出 property, oldValue, newValue 三個(gè)參數(shù)。

package com.imooc.kotlin.delegate

import kotlin.properties.Delegates

class Person{
    var address: String by Delegates.observable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
        println("property: ${property.name}  oldValue: $oldValue  newValue: $newValue")
    })
}

fun main(args: Array<String>) {
    val person = Person().apply { address = "ShangHai" }
    person.address = "BeiJing"
    person.address = "ShenZhen"
    person.address = "GuangZhou"
}

運(yùn)行結(jié)果:

property: address  oldValue: NanJing  newValue: ShangHai
property: address  oldValue: ShangHai  newValue: BeiJing
property: address  oldValue: BeiJing  newValue: ShenZhen
property: address  oldValue: ShenZhen  newValue: GuangZhou
Process finished with exit code 0

2.3 Delegates.vetoable()的基本使用

Delegates.vetoable()代理主要用于監(jiān)控屬性值發(fā)生變更,類似于一個(gè)觀察者,當(dāng)屬性值被修改后會(huì)往外部拋出一個(gè)變更的回調(diào)。它需要傳入兩個(gè)參數(shù),一個(gè)是initValue初始化的值,另一個(gè)就是回調(diào)lamba, 回調(diào)出property, oldValue, newValue三個(gè)參數(shù)。與observable不同的是這個(gè)回調(diào)會(huì)返回一個(gè)Boolean值,來決定此次屬性值是否執(zhí)行修改。

package com.imooc.kotlin.delegate

import kotlin.properties.Delegates

class Person{
    var address: String by Delegates.vetoable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
        println("property: ${property.name}  oldValue: $oldValue  newValue: $newValue")
        return@vetoable newValue == "BeiJing"
    })
}

fun main(args: Array<String>) {
    val person = Person().apply { address = "NanJing" }
    person.address = "BeiJing"
    person.address = "ShangHai"
    person.address = "GuangZhou"
    println("address is ${person.address}")
}

3. 常見屬性代理的源碼分析

以上我們介紹了常見的屬性代理基本使用,如果僅僅停留在使用的階段,確實(shí)有點(diǎn)low了, 那么讓我們一起先來揭開它們的第一層外衣。先來看波Kotlin標(biāo)準(zhǔn)庫源碼中常見的屬性代理包結(jié)構(gòu)。

3.1 源碼包結(jié)構(gòu)

圖片描述

3.2 關(guān)系類圖

圖片描述

  • Delegates : 是一個(gè)代理單例對(duì)象,里面有 notNull、observable、vetoable 靜態(tài)方法,每個(gè)方法返回不同的類型代理對(duì)象;

  • NotNullVar : notNull 方法返回代理對(duì)象的類;

  • ObserableProperty : observable、vetoable方法返回代理對(duì)象的類;

  • ReadOnlyProperty : 只讀屬性代理對(duì)象的通用接口;

  • ReadWriteProperty : 讀寫屬性代理對(duì)象的通用接口。

3.3 Delegates.notNull()源碼分析

notNull() 首先是一個(gè)方法,返回的是一個(gè) NotNullVar 屬性代理實(shí)例;那么它處理核心邏輯就是NotNullVar 內(nèi)部的 setValue 和 getValue 方法,一起來瞅一眼。

  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
    }

通過源碼可以看到一旦 getValue 中的 value 是為 null,那么就會(huì)拋出一個(gè) IllegalStateException,也就是在使用該屬性之前沒有做初始化。實(shí)際上可以理解在訪問器 getter 加了一層判空的代理實(shí)現(xiàn)。

3.4 Delegates.observable()源碼分析

observable() 是一個(gè)方法,返回的是一個(gè) ObservableProperty 屬性代理實(shí)例;那它是怎么做到在屬性值發(fā)生變化通知到外部的呢,其實(shí)很簡(jiǎn)單,首先在內(nèi)部保留一個(gè) oldValue 用于存儲(chǔ)上一次的值,然后就在 ObservableProperty 類 setValue 方法執(zhí)行真正賦值之后再向外部拋出了一個(gè) afterChange 的回調(diào),并且把 oldValue,newValue,property 回調(diào)到外部,最終利用 onChange 方法回調(diào)到最外層。

 public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
 public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
        ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
 }   

3.5 Delegates.vetoable() 源碼分析

vetoable() 是一個(gè)方法,返回的是一個(gè) ObservableProperty 屬性代理實(shí)例;通過上面源碼就可以發(fā)現(xiàn),在 setValue 方法中執(zhí)行真正賦值之前,會(huì)有一個(gè)判斷邏輯,根據(jù) beforeChange 回調(diào)方法返回的 Boolean 決定是否繼續(xù)執(zhí)行下面的真正賦值操作。如果 beforChange() 返回 false 就終止此次賦值,那么 observable 也不能得到回調(diào),如果返回 true 則會(huì)繼續(xù)此次賦值操作,并執(zhí)行 observable 的回調(diào)。

4. 屬性代理背后的原理和源碼反編譯分析

如果說第三節(jié)是揭開屬性代理第一層外衣,那么第四節(jié)將是揭開最后一層外衣了,你會(huì)看到屬性代理真正背后的原理,看完你會(huì)發(fā)現(xiàn)其實(shí)挺簡(jiǎn)單的。不多說先上一個(gè)簡(jiǎn)單例子

class Teacher {
    var name: String by Delegates.notNull()
    var age: Int by Delegates.notNull()
}

實(shí)際上,以上那行代碼是經(jīng)歷了兩個(gè)步驟:

class Teacher {
    private val delegateString: ReadWriteProperty<Teacher, String> = Delegates.notNull()
    private val delegateInt: ReadWriteProperty<Teacher, Int> = Delegates.notNull()
    var name: String by delegateString
    var age: Int by delegateInt
}

Kotlin 反編譯后 Java 源碼:

public final class Teacher {
   // $FF: synthetic field
   //關(guān)鍵點(diǎn)一
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Teacher.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Teacher.class), "age", "getAge()I"))};
   //關(guān)鍵點(diǎn)二
   @NotNull
   private final ReadWriteProperty name$delegate;
   @NotNull
   private final ReadWriteProperty age$delegate;

   //關(guān)鍵點(diǎn)三
   @NotNull
   public final String getName() {
      return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
   }

   public final int getAge() {
      return ((Number)this.age$delegate.getValue(this, $$delegatedProperties[1])).intValue();
   }

   public final void setAge(int var1) {
      this.age$delegate.setValue(this, $$delegatedProperties[1], var1);
   }

   public Teacher() {
      this.name$delegate = Delegates.INSTANCE.notNull();
      this.age$delegate = Delegates.INSTANCE.notNull();
   }
}

分析過程:

  1. 首先, Teacher 類的 name 和 age 屬性會(huì)自動(dòng)生成對(duì)應(yīng)的 setter,getter 方法,并且會(huì)自動(dòng)生成對(duì)應(yīng)的name$delegate、age$delegate 委托對(duì)象,如代碼中標(biāo)識(shí)的關(guān)鍵點(diǎn)二。
  2. 然后,$$delegatedProperties 的 KProperty 數(shù)組中會(huì)保存通過 Kotlin 反射出當(dāng)前 Teacher 類中的中name,age 屬性,反射出來每個(gè)屬性單獨(dú)對(duì)應(yīng)保存在 KProperty 數(shù)組中。
  3. 然后,在對(duì)應(yīng)屬性 setter,getter 方法中是把具體的實(shí)現(xiàn)委托給對(duì)應(yīng)的name$delegate、age$delegate 對(duì)象的 setValue、getValue 方法來實(shí)現(xiàn)的,如代碼中標(biāo)識(shí)的關(guān)鍵點(diǎn)三。
  4. 最后,在 delegate 對(duì)象中的 setValue 和 getValue 方法中的傳入對(duì)應(yīng)反射出來的屬性以及相應(yīng)的值。

5. 自定義屬性代理

有以上的介紹,自己寫個(gè)自定義的屬性代理應(yīng)該很簡(jiǎn)單了吧。實(shí)現(xiàn)一個(gè)簡(jiǎn)單的屬性代理最基本架子就是 setValue,getValue 方法且無需實(shí)現(xiàn)任何的接口。

在 Android 中 SharedPreferences 實(shí)際上就是個(gè)很好場(chǎng)景,因?yàn)樗婕暗搅藢傩源鎯?chǔ)和讀取。自定義屬性代理實(shí)現(xiàn) Android中SharedPreferences 可以直接實(shí)現(xiàn)自帶的 ReadWriteProperty 接口,當(dāng)然也可以自己去寫一個(gè)類然后去定義相應(yīng)的 setValue 方法和 getValue 方法。

class PreferenceDelegate<T>(private val context: Context, private val name: String, private val default: T, private val prefName: String = "default")
	: ReadWriteProperty<Any?, T> {
	private val prefs: SharedPreferences by lazy {
		context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
	}

	override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("setValue from delegate")
        return getPreference(key = name)
	}

	override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        println("setValue from delegate")
		putPreference(key = name, value = value)
	}

	private fun getPreference(key: String): T {
		return when (default) {
			is String -> prefs.getString(key, default)
			is Long -> prefs.getLong(key, default)
			is Boolean -> prefs.getBoolean(key, default)
			is Float -> prefs.getFloat(key, default)
			is Int -> prefs.getInt(key, default)
			else -> throw IllegalArgumentException("Unknown Type.")
		} as T
	}

	private fun putPreference(key: String, value: T) = with(prefs.edit()) {
		when (value) {
			is String -> putString(key, value)
			is Long -> putLong(key, value)
			is Boolean -> putBoolean(key, value)
			is Float -> putFloat(key, value)
			is Int -> putInt(key, value)
			else -> throw IllegalArgumentException("Unknown Type.")
		}
	}.apply()

}

6. 總結(jié)

到這里屬性代理的內(nèi)容就結(jié)束了,有沒有覺得 Kotlin 語言糖設(shè)計(jì)還是很巧妙的。雖然很多人抵觸語法糖,但不可否認(rèn)的是它給我們開發(fā)在效率上帶來了很大的提升。有時(shí)候我們更多地是需要透過語法糖外衣,看到其背后的原理,弄清整個(gè)語法糖設(shè)計(jì)思路和技巧,應(yīng)該對(duì) Kotlin 屬性擴(kuò)展有了比較深的認(rèn)識(shí)了。