您好,登錄后才能下訂單哦!
這篇文章主要介紹了Kotlin中委托屬性與區間的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
委托屬性
有一些常見的屬性類型,雖然我們可以在每次需要的時候手動實現它們, 但是如果能夠為大家把他們只實現一次并放入一個庫會更好。例如包括
延遲屬性(lazy properties): 其值只在首次訪問時計算,
可觀察屬性(observable properties): 監聽器會收到有關此屬性變更的通知,
把多個屬性儲存在一個映射(map)中,而不是每個存在單獨的字段中。
為了涵蓋這些(以及其他)情況,Kotlin 支持 委托屬性:
class Example { var p: String by Delegate() }
委托屬性 是一種通過委托實現擁有 getter 和可選 setter 的 屬性,并允許實現可復用的自定義屬性。例如:
class Example { var p: String by Delegate() }
委托對象必須實現一個擁有 getValue()
方法的操作符,以及 setValue()
方法來實現讀/寫屬性。些方法將會接受包含對象實例以及屬性元數據作為額外參數。當一個類聲明委托屬性時,編譯器生成的代碼會和如下 Java 代碼相似。
public final class Example { @NotNull private final Delegate p$delegate = new Delegate(); // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Example.class), "p", "getP()Ljava/lang/String;"))}; @NotNull public final String getP() { return this.p$delegate.getValue(this, $$delegatedProperties[0]); } public final void setP(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.p$delegate.setValue(this, $$delegatedProperties[0], var1); } }
一些靜態屬性元數據被加入到類中,委托在類的構造函數中初始化,并在每次讀寫屬性時調用。
委托實例
在上面的例子中,創建了一個新的委托實例來實現屬性。這就要求委托的實現是有狀態的,例如,當其內部緩存計算結果時:
class StringDelegate { private var cache: String? = null operator fun getValue(thisRef: Any?, property: KProperty<*>): String { var result = cache if (result == null) { result = someOperation() cache = result } return result } }
與此同時,當需要額外的參數時,需要建立新的委托實例,并將其傳遞到構造器中。
class Example { private val nameView by BindViewDelegate<TextView>(R.id.name) }
但也有一些情況是只需要一個委托實例來實現任何屬性的:當委托是無狀態,并且它所需要的唯一變量就是已經提供好的包含對象實例和委托名稱時,可以通過將其聲明為 object 來替代 class 實現一個單例委托。
舉個例子,下面的單例委托從 Android Activity 中取回與給定 tag 相匹配的 Fragment。
object FragmentDelegate { operator fun getValue(thisRef: Activity, property: KProperty<*>): Fragment? { return thisRef.fragmentManager.findFragmentByTag(property.name) } }
類似地,任何已有類都可以通過擴展變成委托。getValue()
和 setValue()
也可以被聲明成 擴展方法 來實現。Kotlin 已經提供了內置的擴展方法來允許將 Map and MutableMap 實例用作委托,屬性名作為其中的鍵。
如果你選擇復用相同的局部委托實例來在一個類中實現多屬性,你需要在構造函數中初始化實例。
注意:從 Kotlin 1.1 開始,也可以聲明 方法局部變量聲明為委托屬性。在這種情況下,委托可以直到該變量在方法內部聲明的時候才去初始化,而不必在構造函數中就執行初始化。
泛型委托
委托方法也可以被聲明成泛型的,這樣一來不同類型的屬性就可以復用同一個委托類了。
private var maxDelay: Long by SharedPreferencesDelegate<Long>()
然而,如果像上例那樣對基本類型使用泛型委托的話,即便聲明的基本類型非空,也會在每次讀寫屬性的時候觸發裝箱和拆箱的操作。
說明:對于非空基本類型的委托屬性來說,最好使用給定類型的特定委托類而不是泛型委托來避免每次訪問屬性時增加裝箱的額外開銷。
標準委托:lazy()
針對常見情形,Kotlin 提供了一些標準委托,如 Delegates.notNull()
、 Delegates.observable()
和 lazy()
。
lazy()
是一個在第一次讀取時通過給定的 lambda 值來計算屬性的初值,并返回只讀屬性的委托。
private val dateFormat: DateFormat by lazy { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
這是一種簡潔的延遲高消耗的初始化至其真正需要時的方式,在保留代碼可讀性的同時提升了性能。
需要注意的是,lazy()
并不是內聯函數,傳入的 lambda 參數也會被編譯成一個額外的 Function 類,并且不會被內聯到返回的委托對象中。
經常被忽略的一點是 lazy()
有可選的 mode 參數 來決定應該返回 3 種委托的哪一種:
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) }
默認模式 LazyThreadSafetyMode.SYNCHRONIZED
將提供相對耗費昂貴的 雙重檢查鎖 來保證一旦屬性可以從多線程讀取時初始化塊可以安全地執行。
如果你確信屬性只會在單線程(如主線程)被訪問,那么可以選擇 LazyThreadSafetyMode.NONE
來代替,從而避免使用鎖的額外開銷。
val dateFormat: DateFormat by lazy(LazyThreadSafetyMode.NONE) { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
區間
區間 是 Kotlin 中用來代表一個有限的值集合的特殊表達式。值可以是任何 Comparable 類型。 這些表達式的形式都是創建聲明了 ClosedRange 接口的方法。創建區間的主要方法是 .. 操作符方法。
包含
區間表達式的主要作用是使用 in 和 !in 操作符實現包含和不包含。
if (i in 1..10) { println(i) }
該實現針對非空基本類型的區間(包括 Int、Long、Byte、Short、Float、Double 以及 Char 的值)實現了優化,所以上面的代碼可以被優化成這樣:
if(1 <= i && i <= 10) { System.out.println(i); }
零額外支出并且沒有額外對象開銷。區間也可以被包含在 when 表達式中:
val message = when (statusCode) { in 200..299 -> "OK" in 300..399 -> "Find it somewhere else" else -> "Oops" }
相比一系列的 if{…} else if{…}
代碼塊,這段代碼在不降低效率的同時提高了代碼的可讀性。然而,如果在聲明和使用之間有至少一次間接調用的話,range 會有一些微小的額外開銷。
比如下面的代碼:
private val myRange get() = 1..10 fun rangeTest(i: Int) { if (i in myRange) { println(i) } }
在編譯后會創建一個額外的 IntRange 對象:
private final IntRange getMyRange() { return new IntRange(1, 10); } public final void rangeTest(int i) { if(this.getMyRange().contains(i)) { System.out.println(i); } }
將屬性的 getter 聲明為 inline 的方法也無法避免這個對象的創建。這是 Kotlin 1.1 編譯器可以優化的一個點。至少通過這些特定的區間類避免了裝箱操作。
說明:盡量在使用時直接聲明非空基本類型的區間,不要間接調用,來避免額外區間類的創建。或者直接聲明為常量來復用。
區間也可以用于其他實現了 Comparable 的非基本類型。
if (name in "Alfred".."Alicia") { println(name) }
在這種情況下,最終實現并不會優化,而且總是會創建一個 ClosedRange 對象,如下面編譯后的代碼所示:
if(RangesKt.rangeTo((Comparable)"Alfred", (Comparable)"Alicia") .contains((Comparable)name)) { System.out.println(name); }
迭代:for 循環
整型區間 (除了 Float 和 Double之外其他的基本類型)也是 級數:它們可以被迭代。這就可以將經典 Java 的 for 循環用一個更短的表達式替代。
for (i in 1..10) { println(i) }
經過編譯器優化后的代碼實現了零額外開銷:
int i = 1; byte var3 = 10; if(i <= var3) { while(true) { System.out.println(i); if(i == var3) { break; } ++i; } }
如果要反向迭代,可以使用 downTo()
中綴方法來代替 ..:
for (i in 10 downTo 1) { println(i) }
編譯之后,這也實現了零額外開銷:
int i = 10; byte var3 = 1; if(i >= var3) { while(true) { System.out.println(i); if(i == var3) { break; } --i; } }
然而,其他迭代器參數并沒有如此好的優化。反向迭代還有一種結果相同的方式,使用 reversed()
方法結合區間:
for (i in (1..10).reversed()) { println(i) }
編譯后的代碼并沒有看起來那么少:
IntProgression var10000 = RangesKt.reversed((IntProgression)(new IntRange(1, 10))); int i = var10000.getFirst(); int var3 = var10000.getLast(); int var4 = var10000.getStep(); if(var4 > 0) { if(i > var3) { return; } } else if(i < var3) { return; } while(true) { System.out.println(i); if(i == var3) { return; } i += var4; }
會創建一個臨時的 IntRange 對象來代表區間,然后創建另一個 IntProgression 對象來反轉前者的值。
事實上,任何結合不止一個方法來創建遞進都會生成類似的至少創建兩個微小遞進對象的代碼。
這個規則也適用于使用 step() 中綴方法來操作遞進的步驟,即使只有一步:
for (i in 1..10 step 2) { println(i) }
一個次要提示,當生成的代碼讀取 IntProgression 的 last 屬性時會通過對邊界和步長的小小計算來決定準確的最后值。在上面的代碼中,最終值是 9。
最后,until()
中綴函數對于迭代也很有用,該函數(執行結果)不包含最大值。
for (i in 0 until size) { println(i) }
遺憾的是,編譯器并沒有針對這個經典的包含區間圍優化,迭代器依然會創建區間對象:
IntRange var10000 = RangesKt.until(0, size); int i = var10000.getFirst(); int var1 = var10000.getLast(); if(i <= var1) { while(true) { System.out.println(i); if(i == var1) { break; } ++i; } }
這是 Kotlin 1.1 可以提升的另一個點,與此同時,可以通過這樣寫來優化代碼:
for (i in 0..size - 1) { println(i) }
說明:
for 循環內部的迭代,最好只用區間表達式的一個單獨方法來調用 .. 或 downTo()
來避免額外臨時遞進對象的創建。
迭代:forEach()
作為 for 循環的替代,使用區間內聯的擴展方法 forEach()
來實現相似的效果可能更吸引人。
(1..10).forEach { println(it) }
但如果仔細觀察這里使用的 forEach()
方法簽名的話,你就會注意到并沒有優化區間,而只是優化了 Iterable,所以需要創建一個 iterator。下面是編譯后代碼的 Java 形式:
Iterable $receiver$iv = (Iterable)(new IntRange(1, 10)); Iterator var1 = $receiver$iv.iterator(); while(var1.hasNext()) { int element$iv = ((IntIterator)var1).nextInt(); System.out.println(element$iv); }
這段代碼相比前者更為低效,原因是為了創建一個 IntRange 對象,還需要額外創建 IntIterator。但至少它還是生成了基本類型的值。迭代區間時,最好只使用 for 循環而不是區間上的 forEach()
方法來避免額外創建一個迭代器。
迭代:集合
Kotlin 標準庫提供了內置的 indices 擴展屬性來生成數組和 Collection 的區間。
val list = listOf("A", "B", "C") for (i in list.indices) { println(list[i]) }
令人驚訝的是,對這個 indices 的迭代得到了編譯器的優化:
List list = CollectionsKt.listOf(new String[]{"A", "B", "C"}); int i = 0; int var2 = ((Collection)list).size() - 1; if(i <= var2) { while(true) { Object var3 = list.get(i); System.out.println(var3); if(i == var2) { break; } ++i; } }
從上面的代碼中我們可以看到沒有創建 IntRange 對象,列表的迭代是以最高效率的方式運行的。
這適用于數組和實現了 Collection 的類,所以你如果期望相同的迭代器性能的話,可以嘗試在特定的類上使用自己的 indices 擴展屬性。
inline val SparseArray<*>.indices: IntRange get() = 0..size() - 1 fun printValues(map: SparseArray<String>) { for (i in map.indices) { println(map.valueAt(i)) } }
但編譯之后,我們可以發現這并沒有那么高效率,因為編譯器無法足夠智能地避免區間對象的產生:
public static final void printValues(@NotNull SparseArray map) { Intrinsics.checkParameterIsNotNull(map, "map"); IntRange var10002 = new IntRange(0, map.size() - 1); int i = var10002.getFirst(); int var2 = var10002.getLast(); if(i <= var2) { while(true) { Object $receiver$iv = map.valueAt(i); System.out.println($receiver$iv); if(i == var2) { break; } ++i; } } }
所以,我會建議你避免聲明自定義的 lastIndex 擴展屬性:
inline val SparseArray<*>.lastIndex: Int get() = size() - 1 fun printValues(map: SparseArray<String>) { for (i in 0..map.lastIndex) { println(map.valueAt(i)) } }
說明:當迭代沒有聲明 Collection 的自定義集合 時,直接在 for 循環中寫自己的序列區間而不是依賴方法或屬性來生成區間,從而避免區間對象的創建。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Kotlin中委托屬性與區間的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。