Kotlin Vocabulary | Kotlin 內建代理

代理能夠幫助您將任務委託給其餘對象,從而帶來更佳的代碼複用性,您能夠從 咱們以前的文章 瞭解到更多信息。Kotlin 不只可讓您經過 by 關鍵字輕鬆實現代理,還在標準庫中提供了像 lazy()、observable()、vetoable() 以及 notNull() 這樣的內建代理。接下來就讓咱們開始瞭解這些內建代理的使用,以及其內部的實現原理。html

lazy()

lazy() 函數是一個屬性代理,它能夠幫您在第一次訪問屬性時對它們進行惰性初始化。這個函數在建立昂貴對象時十分有用。git

lazy() 函數接收兩個參數,LazyThreadSafetyMode 枚舉值與一個 lambda 表達式。github

LazyThreadSafetyMode 用於指定初始化過程如何在不一樣線程間進行同步,它的默認值是 LazyThreadSafetyMode.SYNCHRONIZED。這意味着初始化操做是線程安全的,但代價是顯式同步會對性能形成輕微影響。設計模式

lambda 表達式會在屬性第一次被訪問時執行,而它的值將會被存儲以用於接下來的訪問。api

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Person(name: String, lastname: String) {
   val fullname: String by lazy() {
     name + lastname
   }
   //…
}
複製代碼

內部原理

在查看反編譯後的 Java 代碼時,咱們能夠看到 Kotlin 編譯器爲惰性 (lazy) 代理建立了一個 Lazy 類型的引用:安全

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

@NotNull
private final Lazy fullname$delegate;
複製代碼

這一代理經過調用 LazyKt.lazy() 函數,並傳入您定義的 lambda 表達式與線程安全模式參數來進行初始化:markdown

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

  this.fullname$delegate = LazyKt.lazy((Function0)(new Function0() {
     @NotNull
     public final String invoke() {
        return name + lastname;
     }
  }));
複製代碼

讓咱們來觀察 lazy() 的源碼。因爲 lazy() 函數默認使用 LazyThreadSafetyMode.SYNCHRONIZED 參數,所以它將返回一個 SynchronizedLazyImpl 類型的 Lazy 對象:jvm

public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
   SynchronizedLazyImpl(initializer)
複製代碼

當屬性代理第一次被訪問時,SynchronizedLazyImplgetValue() 函數就會被調用,這個函數將會在一個 synchronized 塊中對屬性進行初始化:ide

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
           }
       }
   }
複製代碼

這樣就能保證惰性對象會以線程安全的方式初始化,但同時也引入了由 synchronized 塊帶來的額外開銷。函數

注意: 若是您肯定資源會在單線程中被初始化,您能夠向 lazy() 傳入 LazyThreadSafetyMode.NONE,這樣函數就不會在惰性初始化時使用 synchronized 塊。不過請記住,LazyThreadSafetyMode.NONE 不會改變惰性初始化的同步特性。因爲惰性初始化是同步的,因此在第一次訪問時仍會消耗與非惰性初始化過程相同的時間,這意味着那些初始化過程較爲耗時的對象仍會在被訪問時阻塞 UI 線程。

1val lazyValue: String by lazy(LazyThreadSafetyMode.NONE) {「lazy」}
複製代碼

惰性初始化能夠幫助初始化昂貴資源,但對於諸如 String 一類的簡單對象,因爲 lazy() 函數須要生成 LazyKProperty 這樣的額外對象,反而會增長整個過程的開銷。

Observable

Delegates.observable() 是另外一個 Kotlin 標準庫中內建的代理。觀察者模式是一種設計模式,在這一模式中,一個對象會維護一個它的從屬者的列表,這些從屬者即被稱爲觀察者。對象會在它本身的狀態改變時對觀察者進行通知。這一模式十分契合多個對象須要在某個值發生改變時獲得通知的狀況,能夠避免實現爲從屬對象週期調用和檢查資源是否更新。

observable() 函數接收兩個參數: 初始化值與一個當值發生改變時會被調用的監聽處理器。observable() 會建立一個 ObservableProperty 對象,用於在每次 setter 被調用時執行您傳給代理的 lambda 表達式。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Person {
    var address: String by Delegates.observable("not entered yet!") {
        property, oldValue, newValue ->
        // 執行更新操做
    }
}
複製代碼

經過觀察反編譯後的 Person 類型,咱們能夠看到 Kotlin 編譯器生成了一個繼承 ObservableProperty 的類。這個類同時也實現了一個叫作 afterChange() 的函數,這個函數持有您傳遞給 observable 代理的 lambda 函數。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) {
               // 執行更新操做
}
複製代碼

afterChange() 函數由父 ObservableProperty 類的 setter 調用,這意味着每當調用者爲 address 設置一個新的值,setter 就會自動調用 afterChange() 函數,結果就會使全部的監聽器都會收到有關改變的通知。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Person {
    var address: String by Delegates.observable("not entered yet!") {
        property, oldValue, newValue ->
        // 執行更新操做
    }
}
複製代碼

您還能夠從反編譯後的代碼中看到 beforeChange() 函數的調用,observable 代理不會使用 beforeChange()。但對於您接下來會看到的 vetoable(),這一函數倒是功能實現的基礎。

vetoable

vetoable() 是一個內建代理,屬性將否決權委託給它的值。與 observable() 代理相似,vetoable() 一樣接受兩個參數: 初始值與監聽器,當任何調用者想要修改屬性值時,監聽器就會被調用。

<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

var address: String by Delegates.vetoable("") {
       property, oldValue, newValue ->
   newValue.length > 14
}
複製代碼

若是 lambda 表達式返回 true,屬性值就會被修改,反之則保持不變。在本例中,若是調用者嘗試使用長度小於 15 個字符的字符串來更新地址的話,當前值就不會發生改變。

觀察反編譯後的 Person 能夠發現,Kotlin 新生成了一個繼承 ObservableProperty 的類,該類中包含了咱們傳入 beforeChange() 函數的 lambda 表達式,setter 會在每次值被設置以前調用 lambda 表達式。

<!-- Copyright 2020 Google LLC.
 SPDX-License-Identifier: Apache-2.0 -->
 
 public final class Person$$special$$inlined$vetoable$1 extends ObservableProperty {
 
   protected boolean beforeChange(@NotNull KProperty property, Object oldValue, Object newValue) {
      Intrinsics.checkParameterIsNotNull(property, "property");
      String newValue = (String)newValue;
      String var10001 = (String)oldValue;
     int var7 = false;
     return newValue.length() > 14;
  }
}
複製代碼

notNull

Kotlin 標準庫中所提供的最後一個內建代理是 Delegates.notNull()notNull() 容許一個屬性能夠延後一段時間初始化,與 lateinit 相似。因爲 notNull() 會爲每一個屬性建立額外的對象,因此大多數狀況下推薦使用 lateinit。不過,您能夠將 notNull() 與原生類型一同使用,這點是 lateinit 所不支持的。

val fullname: String by Delegates.notNull<String>()
複製代碼

notNull() 使用一種特殊類型的 ReadWriteProperty: NotNullVar。

咱們能夠查看反編譯後的代碼,下面的例子中使用 notNull() 函數初始化了 fullname 屬性:

this.fullname$delegate = Delegates.INSTANCE.notNull();
複製代碼

該函數返回了一個 NotNullVar 對象:

public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
複製代碼

NotNullVar 類型持有一個泛型的可空內部引用,若是在初始化值以前有任何代碼調用 getter,則拋出 IllegalStateException()

<!-- Copyright 2020 Google LLC.
 SPDX-License-Identifier: Apache-2.0 -->
 
 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
   }
}
複製代碼

有了 Kotlin 標準庫提供的這組內建代理,您無需再編寫、維護與從新發明這些功能。這些內建代理能夠幫您惰性初始化字段、容許原生類型延遲加載、監聽並在值發生改變時得到通知,甚至能夠否決屬性值更改。

相關文章
相關標籤/搜索