Kotlin作用域函數(shù)之間的區(qū)別和使用場(chǎng)景詳解
作用域函數(shù)
Kotlin 的作用域函數(shù)有五種:let、run、with、apply 以及 also。
這些函數(shù)基本上做了同樣的事情:在一個(gè)對(duì)象上執(zhí)行一個(gè)代碼塊。
下面是作用域函數(shù)的典型用法:
val adam = Person("Adam").apply { age = 20 city = "London" } println(adam)
如果不使用 apply 來(lái)實(shí)現(xiàn),每次給新創(chuàng)建的對(duì)象屬性賦值時(shí)就必須重復(fù)其名稱。
val adam = Person("Adam") adam.age = 20 adam.city = "London" println(adam)
作用域函數(shù)沒(méi)有引入任何新的技術(shù),但是它們可以使你的代碼更加簡(jiǎn)潔易讀。
事實(shí)上,同樣的功能可能多個(gè)作用域函數(shù)都能實(shí)現(xiàn),但我們應(yīng)該根據(jù)不同的場(chǎng)景和需求來(lái)使用合適的作用域函數(shù),以求更優(yōu)雅的實(shí)現(xiàn)。
如果想直接查看作用域函數(shù)之間的區(qū)別與使用場(chǎng)景歸納表,請(qǐng)點(diǎn)擊這里。
下面我們將詳細(xì)描述這些作用域函數(shù)的區(qū)別及約定用法。
區(qū)別
作用域函數(shù)之間有兩個(gè)主要區(qū)別:
- 引用上下文對(duì)象的方式
- 返回值
引用上下文對(duì)象的方式:this 還是 it
在作用域函數(shù)的 lambda 表達(dá)式里,上下文對(duì)象可以不使用其實(shí)際名稱而是使用一個(gè)更簡(jiǎn)短的引用(this 或 it)來(lái)訪問(wèn)。
作用域函數(shù)引用上下文對(duì)象有兩種方式:
- 作為 lambda 表達(dá)式的接收者(this): run、with、apply
- 作為 lambda 表達(dá)式的參數(shù)(it): let、also
fun main() { val str = "Hello" // this str.run { println("The receiver string length: $length") //println("The receiver string length: ${this.length}") // 和上句效果相同 } // it str.let { println("The receiver string's length is ${it.length}") } }
作為 lambda 表達(dá)式的接收者
run、with 以及 apply 將上下文對(duì)象作為 lambda 表達(dá)式接收者,通過(guò)關(guān)鍵字 this 引用上下文對(duì)象??梢允÷?this,使代碼更簡(jiǎn)短。
使用場(chǎng)景:主要對(duì)上下文對(duì)象的成員進(jìn)行操作(訪問(wèn)屬性或調(diào)用函數(shù))。
val adam = Person("Adam").apply { age = 20 // 和 this.age = 20 或者 adam.age = 20 一樣 city = "London" } println(adam)
作為 lambda 表達(dá)式的參數(shù)
let 及 also 將上下文對(duì)象作為 lambda 表達(dá)式參數(shù)。如果沒(méi)有指定參數(shù)名,對(duì)象可以用隱式默認(rèn)名稱 it 訪問(wèn)。it 比 this 簡(jiǎn)短,帶有 it 的表達(dá)式通常更容易閱讀。然而,當(dāng)調(diào)用對(duì)象函數(shù)或?qū)傩詴r(shí),不能像 this 這樣隱式地訪問(wèn)對(duì)象。
使用場(chǎng)景:主要對(duì)上下文對(duì)象進(jìn)行操作,作為參數(shù)使用。
fun getRandomInt(): Int { return Random.nextInt(100).also { writeToLog("getRandomInt() generated value $it") } } val i = getRandomInt()
此外,當(dāng)將上下文對(duì)象作為參數(shù)傳遞時(shí),可以為上下文對(duì)象指定在作用域內(nèi)的自定義名稱(為了提高代碼的可讀性)。
fun getRandomInt(): Int { return Random.nextInt(100).also { value -> writeToLog("getRandomInt() generated value $value") } } val i = getRandomInt()
返回值
根據(jù)返回結(jié)果,作用域函數(shù)可以分為以下兩類:
- 返回上下文對(duì)象:apply、also
- 返回 lambda 表達(dá)式結(jié)果:let、run、with
可以根據(jù)在代碼中的后續(xù)操作來(lái)選擇適當(dāng)?shù)暮瘮?shù)。
返回上下文對(duì)象
apply 及 also 的返回值是上下文對(duì)象本身。因此,它們可以作為輔助步驟包含在調(diào)用鏈中:你可以繼續(xù)在同一個(gè)對(duì)象上進(jìn)行鏈?zhǔn)胶瘮?shù)調(diào)用。
val numberList = mutableListOf<Double>() numberList.also { println("Populating the list") } .apply { add(2.71) add(3.14) add(1.0) } .also { println("Sorting the list") } .sort()
它們還可以用在返回上下文對(duì)象的函數(shù)的 return 語(yǔ)句中。
fun getRandomInt(): Int { return Random.nextInt(100).also { writeToLog("getRandomInt() generated value $it") } } val i = getRandomInt()
返回lambda表達(dá)式結(jié)果
let、run 及 with 返回 lambda 表達(dá)式的結(jié)果。所以,在需要使用其結(jié)果給一個(gè)變量賦值,或者在需要對(duì)其結(jié)果進(jìn)行鏈?zhǔn)讲僮鞯惹闆r下,可以使用它們。
val numbers = mutableListOf("one", "two", "three") val countEndsWithE = numbers.run { add("four") add("five") count { it.endsWith("e") } } println("There are $countEndsWithE elements that end with e.")
此外,還可以忽略返回值,僅使用作用域函數(shù)為變量創(chuàng)建一個(gè)臨時(shí)作用域。
val numbers = mutableListOf("one", "two", "three") with(numbers) { val firstItem = first() val lastItem = last() println("First item: $firstItem, last item: $lastItem") }
約定用法
let
上下文對(duì)象 作為 lambda 表達(dá)式的 參數(shù)(it)來(lái)訪問(wèn)。 返回值 是 lambda 表達(dá)式的結(jié)果。
let 可用于在調(diào)用鏈的結(jié)果上調(diào)用一個(gè)或多個(gè)函數(shù)。例如,以下代碼打印對(duì)集合的兩個(gè)操作的結(jié)果:
val numbers = mutableListOf("one", "two", "three", "four", "five") val resultList = numbers.map { it.length }.filter { it > 3 } println(resultList)
使用 let,可以寫成這樣:
val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map { it.length }.filter { it > 3 }.let { println(it) // 如果需要可以調(diào)用更多函數(shù) }
若代碼塊僅包含以 it 作為參數(shù)的單個(gè)函數(shù),則可以使用方法引用(::)代替 lambda 表達(dá)式:
val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map { it.length }.filter { it > 3 }.let(::println)
let 經(jīng)常用于 僅使用非空值執(zhí)行代碼塊。如需對(duì)非空對(duì)象執(zhí)行操作,可對(duì)其使用安全調(diào)用操作符 ?. 并調(diào)用 let 在 lambda 表達(dá)式中執(zhí)行操作。
val str: String? = "Hello" //processNonNullString(str) // 編譯錯(cuò)誤:str 可能為空 val length = str?.let { println("let() called on $it") processNonNullString(it) // 編譯通過(guò):'it' 在 '?.let { }' 中必不為空 it.length }
使用 let 的另一種情況是引入作用域受限的局部變量以提高代碼的可讀性。如需為上下文對(duì)象定義一個(gè)新變量,可提供其名稱作為 lambda 表達(dá)式參數(shù)來(lái)替默認(rèn)的 it。
val numbers = listOf("one", "two", "three", "four") val modifiedFirstItem = numbers.first().let { firstItem -> println("The first item of the list is '$firstItem'") if (firstItem.length >= 5) firstItem else "!" + firstItem + "!" }.toUpperCase() println("First item after modifications: '$modifiedFirstItem'")
with
一個(gè)非擴(kuò)展函數(shù):上下文對(duì)象作為參數(shù)傳遞,但是在 lambda 表達(dá)式內(nèi)部,它可以作為接收者(this)使用。 返回值是lambda 表達(dá)式結(jié)果。
建議使用 with 來(lái)調(diào)用上下文對(duì)象上的函數(shù),而不使用 lambda 表達(dá)式結(jié)果。 在代碼中,with 可以理解為“對(duì)于這個(gè)對(duì)象,執(zhí)行以下操作?!?/p>
val numbers = mutableListOf("one", "two", "three") with(numbers) { println("'with' is called with argument $this") println("It contains $size elements") }
with 的另一個(gè)使用場(chǎng)景是引入一個(gè)輔助對(duì)象,其屬性或函數(shù)將用于計(jì)算一個(gè)值。
val numbers = mutableListOf("one", "two", "three") val firstAndLast = with(numbers) { "The first element is ${first()}," + " the last element is ${last()}" } println(firstAndLast)
run
上下文對(duì)象 作為 接收者(this)來(lái)訪問(wèn)。 返回值 是 lambda 表達(dá)式結(jié)果。
run 和 with 做同樣的事情,但是調(diào)用方式和 let 一樣——作為上下文對(duì)象的擴(kuò)展函數(shù).
當(dāng) lambda 表達(dá)式同時(shí)包含對(duì)象初始化和返回值的計(jì)算時(shí),run 很有用。
val service = MultiportService("https://example.kotlinlang.org", 80) val result = service.run { port = 8080 query(prepareRequest() + " to port $port") } // 同樣的代碼如果用 let() 函數(shù)來(lái)寫: val letResult = service.let { it.port = 8080 it.query(it.prepareRequest() + " to port ${it.port}") }
除了在接收者對(duì)象上調(diào)用 run 之外,還可以將其用作非擴(kuò)展函數(shù)。 非擴(kuò)展 run 可以使你在需要表達(dá)式的地方執(zhí)行一個(gè)由多個(gè)語(yǔ)句組成的塊。
val hexNumberRegex = run { val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) { println(match.value) }
apply
上下文對(duì)象 作為 接收者(this)來(lái)訪問(wèn)。返回值 是上下文對(duì)象本身。
對(duì)于不返回值且主要在接收者(this)對(duì)象的成員上運(yùn)行的代碼塊使用 apply。apply 的常見(jiàn)情況是對(duì)象配置。這樣的調(diào)用可以理解為“將以下賦值操作應(yīng)用于對(duì)象”。
val adam = Person("Adam").apply { age = 32 city = "London" } println(adam)
將接收者作為返回值,可以輕松地將 apply 包含到調(diào)用鏈中以進(jìn)行更復(fù)雜的處理。
also
上下文對(duì)象作為 lambda 表達(dá)式的參數(shù)(it)來(lái)訪問(wèn)。 返回值 是上下文對(duì)象本身。
also 對(duì)于執(zhí)行一些將上下文對(duì)象作為參數(shù)的操作很有用。 對(duì)于需要引用對(duì)象而不是其屬性與函數(shù)的操作,或者不想屏蔽來(lái)自外部作用域的 this 引用時(shí),請(qǐng)使用 also。
當(dāng)在代碼中看到 also 時(shí),可以將其理解為“并且用該對(duì)象執(zhí)行以下操作”。
val numbers = mutableListOf("one", "two", "three") numbers .also { println("The list elements before adding new one: $it") } .add("four")
下表總結(jié)了Kotlin作用域函數(shù)的主要區(qū)別與使用場(chǎng)景:
函數(shù) | 對(duì)象引用 | 返回值 | 是否是擴(kuò)展函數(shù) | 使用場(chǎng)景 |
---|---|---|---|---|
let | it | Lambda 表達(dá)式結(jié)果 | 是 | 1. 對(duì)一個(gè)非空對(duì)象執(zhí)行 lambda 表達(dá)式 2. 將表達(dá)式作為變量引入為局部作用域中 |
run | this | Lambda 表達(dá)式結(jié)果 | 是 | 對(duì)象配置并且計(jì)算結(jié)果 |
run | - | Lambda 表達(dá)式結(jié)果 | 不是:調(diào)用無(wú)需上下文對(duì)象 | 在需要表達(dá)式的地方運(yùn)行語(yǔ)句 |
with | this | Lambda 表達(dá)式結(jié)果 | 不是:把上下文對(duì)象當(dāng)做參數(shù) | 一個(gè)對(duì)象的一組函數(shù)調(diào)用 |
apply | this | 上下文對(duì)象 | 是 | 對(duì)象配置 |
also | it | 上下文對(duì)象 | 是 | 附加效果 |
參考資料
好了,到此這篇關(guān)于Kotlin作用域函數(shù)之間的區(qū)別和使用場(chǎng)景的文章就介紹到這了,更多相關(guān)Kotlin作用域函數(shù)區(qū)別與使用場(chǎng)景內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android編程之高效開(kāi)發(fā)App的10個(gè)建議
這篇文章主要介紹了Android編程之高效開(kāi)發(fā)App的10個(gè)建議,較為詳細(xì)的分析了Android開(kāi)發(fā)中的常見(jiàn)問(wèn)題與注意事項(xiàng),具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android APT 實(shí)現(xiàn)控件注入框架SqInject的示例
這篇文章主要介紹了Android APT 實(shí)現(xiàn)控件注入框架SqInject的示例,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03Android RecyclerView添加FootView和HeadView
這篇文章主要介紹了Android RecyclerView添加FootView和HeadView的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Android Studio+Servlet+MySql實(shí)現(xiàn)登錄注冊(cè)
對(duì)于大多數(shù)的APP都有登錄注冊(cè)這個(gè)功能,本文就來(lái)介紹一下Android Studio+Servlet+MySql實(shí)現(xiàn)登錄注冊(cè),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05Android編程實(shí)現(xiàn)Toast只顯示最后一條的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)Toast只顯示最后一條的方法,結(jié)合實(shí)例形式總結(jié)了Toast只顯示最后一條的原理與具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-08-08android4.0混淆XmlPullParser報(bào)錯(cuò)原因分析解決
今天,用android4.0在proguard-project.txt中加入 -libraryjars libs/ksoap2-android-assembly-2.6.0-jar-with-dependencies.jar這句話后,混淆時(shí)報(bào)上面的錯(cuò)誤,下面與大家分享下具體的解決方法2013-06-06Android 兩個(gè)Service的相互監(jiān)視實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 兩個(gè)Service的相互監(jiān)視實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10Android開(kāi)發(fā)手冊(cè)RatingBar星級(jí)評(píng)分控件實(shí)例
這篇文章主要為大家介紹了Android開(kāi)發(fā)手冊(cè)RatingBar星級(jí)評(píng)分控件實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06