Kotlin開(kāi)發(fā)筆記之委托屬性與區(qū)間(譯)
前言
本文主要給大家介紹了關(guān)于Kotlin委托屬性與區(qū)間的相關(guān)內(nèi)容,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。
委托屬性
有一些常見(jiàn)的屬性類型,雖然我們可以在每次需要的時(shí)候手動(dòng)實(shí)現(xiàn)它們, 但是如果能夠?yàn)榇蠹野阉麄冎粚?shí)現(xiàn)一次并放入一個(gè)庫(kù)會(huì)更好。例如包括
- 延遲屬性(lazy properties): 其值只在首次訪問(wèn)時(shí)計(jì)算,
- 可觀察屬性(observable properties): 監(jiān)聽(tīng)器會(huì)收到有關(guān)此屬性變更的通知,
- 把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。
為了涵蓋這些(以及其他)情況,Kotlin 支持 委托屬性:
class Example { var p: String by Delegate() }
委托屬性 是一種通過(guò)委托實(shí)現(xiàn)擁有 getter 和可選 setter 的 屬性,并允許實(shí)現(xiàn)可復(fù)用的自定義屬性。例如:
class Example { var p: String by Delegate() }
委托對(duì)象必須實(shí)現(xiàn)一個(gè)擁有 getValue()
方法的操作符,以及 setValue()
方法來(lái)實(shí)現(xiàn)讀/寫屬性。些方法將會(huì)接受包含對(duì)象實(shí)例以及屬性元數(shù)據(jù)作為額外參數(shù)。當(dāng)一個(gè)類聲明委托屬性時(shí),編譯器生成的代碼會(huì)和如下 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); } }
一些靜態(tài)屬性元數(shù)據(jù)被加入到類中,委托在類的構(gòu)造函數(shù)中初始化,并在每次讀寫屬性時(shí)調(diào)用。
委托實(shí)例
在上面的例子中,創(chuàng)建了一個(gè)新的委托實(shí)例來(lái)實(shí)現(xiàn)屬性。這就要求委托的實(shí)現(xiàn)是有狀態(tài)的,例如,當(dāng)其內(nèi)部緩存計(jì)算結(jié)果時(shí):
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 } }
與此同時(shí),當(dāng)需要額外的參數(shù)時(shí),需要建立新的委托實(shí)例,并將其傳遞到構(gòu)造器中。
class Example { private val nameView by BindViewDelegate<TextView>(R.id.name) }
但也有一些情況是只需要一個(gè)委托實(shí)例來(lái)實(shí)現(xiàn)任何屬性的:當(dāng)委托是無(wú)狀態(tài),并且它所需要的唯一變量就是已經(jīng)提供好的包含對(duì)象實(shí)例和委托名稱時(shí),可以通過(guò)將其聲明為 object 來(lái)替代 class 實(shí)現(xiàn)一個(gè)單例委托。
舉個(gè)例子,下面的單例委托從 Android Activity 中取回與給定 tag 相匹配的 Fragment。
object FragmentDelegate { operator fun getValue(thisRef: Activity, property: KProperty<*>): Fragment? { return thisRef.fragmentManager.findFragmentByTag(property.name) } }
類似地,任何已有類都可以通過(guò)擴(kuò)展變成委托。getValue()
和 setValue()
也可以被聲明成 擴(kuò)展方法 來(lái)實(shí)現(xiàn)。Kotlin 已經(jīng)提供了內(nèi)置的擴(kuò)展方法來(lái)允許將 Map and MutableMap 實(shí)例用作委托,屬性名作為其中的鍵。
如果你選擇復(fù)用相同的局部委托實(shí)例來(lái)在一個(gè)類中實(shí)現(xiàn)多屬性,你需要在構(gòu)造函數(shù)中初始化實(shí)例。
注意:從 Kotlin 1.1 開(kāi)始,也可以聲明 方法局部變量聲明為委托屬性。在這種情況下,委托可以直到該變量在方法內(nèi)部聲明的時(shí)候才去初始化,而不必在構(gòu)造函數(shù)中就執(zhí)行初始化。
泛型委托
委托方法也可以被聲明成泛型的,這樣一來(lái)不同類型的屬性就可以復(fù)用同一個(gè)委托類了。
private var maxDelay: Long by SharedPreferencesDelegate<Long>()
然而,如果像上例那樣對(duì)基本類型使用泛型委托的話,即便聲明的基本類型非空,也會(huì)在每次讀寫屬性的時(shí)候觸發(fā)裝箱和拆箱的操作。
說(shuō)明:對(duì)于非空基本類型的委托屬性來(lái)說(shuō),最好使用給定類型的特定委托類而不是泛型委托來(lái)避免每次訪問(wèn)屬性時(shí)增加裝箱的額外開(kāi)銷。
標(biāo)準(zhǔn)委托:lazy()
針對(duì)常見(jiàn)情形,Kotlin 提供了一些標(biāo)準(zhǔn)委托,如 Delegates.notNull()
、 Delegates.observable()
和 lazy()
。
lazy()
是一個(gè)在第一次讀取時(shí)通過(guò)給定的 lambda 值來(lái)計(jì)算屬性的初值,并返回只讀屬性的委托。
private val dateFormat: DateFormat by lazy { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
這是一種簡(jiǎn)潔的延遲高消耗的初始化至其真正需要時(shí)的方式,在保留代碼可讀性的同時(shí)提升了性能。
需要注意的是,lazy()
并不是內(nèi)聯(lián)函數(shù),傳入的 lambda 參數(shù)也會(huì)被編譯成一個(gè)額外的 Function 類,并且不會(huì)被內(nèi)聯(lián)到返回的委托對(duì)象中。
經(jīng)常被忽略的一點(diǎn)是 lazy()
有可選的 mode 參數(shù) 來(lái)決定應(yīng)該返回 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) }
默認(rèn)模式 LazyThreadSafetyMode.SYNCHRONIZED
將提供相對(duì)耗費(fèi)昂貴的 雙重檢查鎖 來(lái)保證一旦屬性可以從多線程讀取時(shí)初始化塊可以安全地執(zhí)行。
如果你確信屬性只會(huì)在單線程(如主線程)被訪問(wèn),那么可以選擇 LazyThreadSafetyMode.NONE
來(lái)代替,從而避免使用鎖的額外開(kāi)銷。
val dateFormat: DateFormat by lazy(LazyThreadSafetyMode.NONE) { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
區(qū)間
區(qū)間 是 Kotlin 中用來(lái)代表一個(gè)有限的值集合的特殊表達(dá)式。值可以是任何 Comparable 類型。 這些表達(dá)式的形式都是創(chuàng)建聲明了 ClosedRange 接口的方法。創(chuàng)建區(qū)間的主要方法是 .. 操作符方法。
包含
區(qū)間表達(dá)式的主要作用是使用 in 和 !in 操作符實(shí)現(xiàn)包含和不包含。
if (i in 1..10) { println(i) }
該實(shí)現(xiàn)針對(duì)非空基本類型的區(qū)間(包括 Int、Long、Byte、Short、Float、Double 以及 Char 的值)實(shí)現(xiàn)了優(yōu)化,所以上面的代碼可以被優(yōu)化成這樣:
if(1 <= i && i <= 10) { System.out.println(i); }
零額外支出并且沒(méi)有額外對(duì)象開(kāi)銷。區(qū)間也可以被包含在 when 表達(dá)式中:
val message = when (statusCode) { in 200..299 -> "OK" in 300..399 -> "Find it somewhere else" else -> "Oops" }
相比一系列的 if{…} else if{…}
代碼塊,這段代碼在不降低效率的同時(shí)提高了代碼的可讀性。然而,如果在聲明和使用之間有至少一次間接調(diào)用的話,range 會(huì)有一些微小的額外開(kāi)銷。
比如下面的代碼:
private val myRange get() = 1..10 fun rangeTest(i: Int) { if (i in myRange) { println(i) } }
在編譯后會(huì)創(chuàng)建一個(gè)額外的 IntRange 對(duì)象:
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 的方法也無(wú)法避免這個(gè)對(duì)象的創(chuàng)建。這是 Kotlin 1.1 編譯器可以優(yōu)化的一個(gè)點(diǎn)。至少通過(guò)這些特定的區(qū)間類避免了裝箱操作。
說(shuō)明:盡量在使用時(shí)直接聲明非空基本類型的區(qū)間,不要間接調(diào)用,來(lái)避免額外區(qū)間類的創(chuàng)建?;蛘咧苯勇暶鳛槌A縼?lái)復(fù)用。
區(qū)間也可以用于其他實(shí)現(xiàn)了 Comparable 的非基本類型。
if (name in "Alfred".."Alicia") { println(name) }
在這種情況下,最終實(shí)現(xiàn)并不會(huì)優(yōu)化,而且總是會(huì)創(chuàng)建一個(gè) ClosedRange 對(duì)象,如下面編譯后的代碼所示:
if(RangesKt.rangeTo((Comparable)"Alfred", (Comparable)"Alicia") .contains((Comparable)name)) { System.out.println(name); }
迭代:for 循環(huán)
整型區(qū)間 (除了 Float 和 Double之外其他的基本類型)也是 級(jí)數(shù):它們可以被迭代。這就可以將經(jīng)典 Java 的 for 循環(huán)用一個(gè)更短的表達(dá)式替代。
for (i in 1..10) { println(i) }
經(jīng)過(guò)編譯器優(yōu)化后的代碼實(shí)現(xiàn)了零額外開(kāi)銷:
int i = 1; byte var3 = 10; if(i <= var3) { while(true) { System.out.println(i); if(i == var3) { break; } ++i; } }
如果要反向迭代,可以使用 downTo()
中綴方法來(lái)代替 ..:
for (i in 10 downTo 1) { println(i) }
編譯之后,這也實(shí)現(xiàn)了零額外開(kāi)銷:
int i = 10; byte var3 = 1; if(i >= var3) { while(true) { System.out.println(i); if(i == var3) { break; } --i; } }
然而,其他迭代器參數(shù)并沒(méi)有如此好的優(yōu)化。反向迭代還有一種結(jié)果相同的方式,使用 reversed()
方法結(jié)合區(qū)間:
for (i in (1..10).reversed()) { println(i) }
編譯后的代碼并沒(méi)有看起來(lá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; }
會(huì)創(chuàng)建一個(gè)臨時(shí)的 IntRange 對(duì)象來(lái)代表區(qū)間,然后創(chuàng)建另一個(gè) IntProgression 對(duì)象來(lái)反轉(zhuǎn)前者的值。
事實(shí)上,任何結(jié)合不止一個(gè)方法來(lái)創(chuàng)建遞進(jìn)都會(huì)生成類似的至少創(chuàng)建兩個(gè)微小遞進(jìn)對(duì)象的代碼。
這個(gè)規(guī)則也適用于使用 step() 中綴方法來(lái)操作遞進(jìn)的步驟,即使只有一步:
for (i in 1..10 step 2) { println(i) }
一個(gè)次要提示,當(dāng)生成的代碼讀取 IntProgression 的 last 屬性時(shí)會(huì)通過(guò)對(duì)邊界和步長(zhǎng)的小小計(jì)算來(lái)決定準(zhǔn)確的最后值。在上面的代碼中,最終值是 9。
最后,until()
中綴函數(shù)對(duì)于迭代也很有用,該函數(shù)(執(zhí)行結(jié)果)不包含最大值。
for (i in 0 until size) { println(i) }
遺憾的是,編譯器并沒(méi)有針對(duì)這個(gè)經(jīng)典的包含區(qū)間圍優(yōu)化,迭代器依然會(huì)創(chuàng)建區(qū)間對(duì)象:
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 可以提升的另一個(gè)點(diǎn),與此同時(shí),可以通過(guò)這樣寫來(lái)優(yōu)化代碼:
for (i in 0..size - 1) { println(i) }
說(shuō)明:
for 循環(huán)內(nèi)部的迭代,最好只用區(qū)間表達(dá)式的一個(gè)單獨(dú)方法來(lái)調(diào)用 .. 或 downTo()
來(lái)避免額外臨時(shí)遞進(jìn)對(duì)象的創(chuàng)建。
迭代:forEach()
作為 for 循環(huán)的替代,使用區(qū)間內(nèi)聯(lián)的擴(kuò)展方法 forEach()
來(lái)實(shí)現(xiàn)相似的效果可能更吸引人。
(1..10).forEach { println(it) }
但如果仔細(xì)觀察這里使用的 forEach()
方法簽名的話,你就會(huì)注意到并沒(méi)有優(yōu)化區(qū)間,而只是優(yōu)化了 Iterable,所以需要?jiǎng)?chuàng)建一個(gè) 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); }
這段代碼相比前者更為低效,原因是為了創(chuàng)建一個(gè) IntRange 對(duì)象,還需要額外創(chuàng)建 IntIterator。但至少它還是生成了基本類型的值。迭代區(qū)間時(shí),最好只使用 for 循環(huán)而不是區(qū)間上的 forEach()
方法來(lái)避免額外創(chuàng)建一個(gè)迭代器。
迭代:集合
Kotlin 標(biāo)準(zhǔn)庫(kù)提供了內(nèi)置的 indices 擴(kuò)展屬性來(lái)生成數(shù)組和 Collection 的區(qū)間。
val list = listOf("A", "B", "C") for (i in list.indices) { println(list[i]) }
令人驚訝的是,對(duì)這個(gè) indices 的迭代得到了編譯器的優(yōu)化:
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; } }
從上面的代碼中我們可以看到?jīng)]有創(chuàng)建 IntRange 對(duì)象,列表的迭代是以最高效率的方式運(yùn)行的。
這適用于數(shù)組和實(shí)現(xiàn)了 Collection 的類,所以你如果期望相同的迭代器性能的話,可以嘗試在特定的類上使用自己的 indices 擴(kuò)展屬性。
inline val SparseArray<*>.indices: IntRange get() = 0..size() - 1 fun printValues(map: SparseArray<String>) { for (i in map.indices) { println(map.valueAt(i)) } }
但編譯之后,我們可以發(fā)現(xiàn)這并沒(méi)有那么高效率,因?yàn)榫幾g器無(wú)法足夠智能地避免區(qū)間對(duì)象的產(chǎn)生:
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; } } }
所以,我會(huì)建議你避免聲明自定義的 lastIndex 擴(kuò)展屬性:
inline val SparseArray<*>.lastIndex: Int get() = size() - 1 fun printValues(map: SparseArray<String>) { for (i in 0..map.lastIndex) { println(map.valueAt(i)) } }
說(shuō)明:當(dāng)?shù)鷽](méi)有聲明 Collection 的自定義集合 時(shí),直接在 for 循環(huán)中寫自己的序列區(qū)間而不是依賴方法或?qū)傩詠?lái)生成區(qū)間,從而避免區(qū)間對(duì)象的創(chuàng)建。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
關(guān)于Kotlin委托你必須重視的幾個(gè)點(diǎn)
委托模式已經(jīng)被證明是實(shí)現(xiàn)繼承的一個(gè)很好的替代方式,下面這篇文章主要給大家介紹了關(guān)于Kotlin委托你必須重視的幾個(gè)點(diǎn),文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01Android應(yīng)用開(kāi)發(fā)中自定義ViewGroup視圖容器的教程
這篇文章主要介紹了Android應(yīng)用開(kāi)發(fā)中自定義ViewGroup視圖容器的教程,重點(diǎn)在于View之間的參數(shù)傳遞,文中還講到了使用ViewDragHelper自定義ViewGroup的方法,需要的朋友可以參考下2016-04-04Android編程之SharedPreferences文件存儲(chǔ)操作實(shí)例分析
這篇文章主要介紹了Android編程之SharedPreferences文件存儲(chǔ)操作方法,實(shí)例分析了SharedPreferences文件操作的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04ToolBar中menu無(wú)法同時(shí)顯示圖標(biāo)和文字問(wèn)題的解決方法
這篇文章主要為大家詳細(xì)介紹了ToolBar中menu無(wú)法同時(shí)顯示圖標(biāo)和文字問(wèn)題的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android實(shí)現(xiàn)Service重啟的方法
這篇文章主要介紹了Android實(shí)現(xiàn)Service重啟的方法,涉及Android操作Service組件實(shí)現(xiàn)服務(wù)重啟的功能,需要的朋友可以參考下2015-05-05Android巧用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)圖片
這篇文章主要為大家詳細(xì)介紹了Android巧用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05Android實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼功能
這篇文章主要介紹了Android實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Android 仿小米鎖屏實(shí)現(xiàn)九宮格解鎖功能(無(wú)需圖片資源)
最近公司要求做個(gè)九宮格解鎖,本人用的是小米手機(jī),看著他那個(gè)設(shè)置鎖屏九宮格很好看,就做了該組件,不使用圖片資源,純代碼實(shí)現(xiàn),感興趣的朋友參考下吧2016-12-12Android中AsyncTask的入門使用學(xué)習(xí)指南
AsyncTask異步任務(wù),用于執(zhí)行耗時(shí)任務(wù)并在UI線程中更新結(jié)果。下面這篇文章主要給大家介紹了關(guān)于Android中AsyncTask入門使用的相關(guān)資料,需要的朋友可以參考下2019-02-02Android_UI 仿QQ側(cè)滑菜單效果的實(shí)現(xiàn)
相信大家對(duì)QQ側(cè)滑菜單的效果已經(jīng)不陌生了吧,側(cè)滑進(jìn)入個(gè)人頭像一側(cè),進(jìn)行對(duì)頭像的更改,我的收藏,QQ錢包,我的文件等一系列的操作,下面小編給大家分享Android_UI 仿QQ側(cè)滑菜單效果的實(shí)現(xiàn),一起看看吧2017-04-04