Kotlin學習筆記(7) — 標準函數 (Scope Functions)

apply, let, run, with, also, takeIf 以及 takeUnless

Andy Lu
7 min readAug 23, 2020

您是否常常看到 Kotlin 的程式碼中,有一些特殊的函式,如:apply、let、run…等等,這些函數是由 Kotlin 的標準函式庫提供,其目的是在對象物件的上下文執行代碼區塊。這些函數稱之為標準函數 (Scope Function)。

使用標準函數有幾個優點:
1. 在這些代碼區塊中,您不需要呼叫對象的名稱就能使用。
2. 提供更清晰的程式碼語意,讓讀程式碼的也成為一件容易的事情。

apply

  • apply 函數可視為一種配置函數:傳入一個接收者,然後呼叫一系列函數來對接收者做設定。
  • 傳入的接收者為傳入的對象物件,回傳值是對象本身。

例如:有一個 File 的實例被建立出來之後,立刻呼叫三個 File 內的函數其設定值。

val menuFile = File("menu-file.txt")
menuFile.setReadable(true)
menuFile.setWritable(true)
menuFile.setExecutable(false)

用 apply 改寫

val menuFile = File("menu-file.txt").apply {
setReadable(true)
setWritable(true)
setExecutable(false)
}
  • 使用 apply 改寫之後,在語意方面,可以明顯地發現,File 執行了三個動作。
  • 另外,因為 File("menu-file.txt") 會直接傳入apply 函數中,所以可以在 apply 函數中直接呼叫 File("menu-file.txt") 的函數。

let

  • let 函數能使某個變數作用於其 lambda 運算式,其回傳值是 lambda 的結果。
  • let 函數中,可以使用 it 表示 傳入的對象。

例如:有一個 list ,我們想要計算第一個項目的平方值。

val firstElement = listOf(1,2,3).first()
val firstItemSquared = firstElement * firstElement

用 let 改寫

val firstItemSquared = listOf(1,2,3).first().let{
it * it
}
  • let 改寫之後,不需要額外的暫存變數 firstElement,在 let 函數中,可以使用 it 關鍵字代表傳入的 listOf(1,2,3).first()

run

  • 與 apply 一樣都是把接收者傳入 lambda 函數中,不一樣的是 run 回傳的是 lambda 的回傳值,apply 沒有回傳值。
  • run 適合用在需要對對象物件作設定並且執行運算取得一個結果。
  • 特別注意的是,當執行 run 函數的值為 null 時, run 的區塊是不會進行操作的。

例如:將 service 對象物件的 port 設定為 8080,並且將service.port利用 query 函式帶入取得 result。

service.port = 8080
val result = query(prepareRequest() + " to port ${service.port}")

用 run 改寫

val result = service.run {
port = 8080
query(prepareRequest() + " to port $port")
}
  • 可以清楚地知道,在 run 函數中,將 port 設定為 8080,
  • 設定完 port之後在 query 函數中,代入 port 的值。
  • 注意到, run 裡面就不需要使用 service.port ,因為 service 已經代入至 run 中,所以可以直接呼叫 port

with

  • with 函數是 run 函數的變形:他們的功能與行為一樣,接收者是使用 with 的對象物件, with 的結果則是 lambda 的結果。
  • 但 with 的呼叫方式不同,使用 with 時,要求引數作為其第一個參數傳入,另外,若執行 run 的對象物件為 null ,則 run 區塊不會進入;反之, with 函數則是會進去操作。
val stringTooLong = with("long long long string"){
length > 10
}
println(stringTooLong)
//true

also

  • 將呼叫 also 的物件,以 it 代入 also 中。回傳的值則是該物件。
  • also 適合針對同一原始物件,透過副作用做事。當看到 also的時候表示,同時還要針對該對象物件執行此動作。

例如:
有一個 MutableList 設定完之後,列印該 List,並且在列印完之後,再增加一個項目。

val numbers = mutableListOf("one", "two", "three")
println("The list elements before adding new one: $numbers")
numbers.add("four")

also 改寫

val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
  • 因為 also 回傳的是傳入對象,所以可以直接串接 add("four")
  • 讀這段程式碼就會清楚的知道:建立完這個 Mutableist 之後做了兩件事。(1). 列印一段文字,裡面包含 List 的內容,(2). 在 List 中新增一個項目。
  • 別忘了,在 also 裡面呼叫對象函數,需要使用 it 關鍵字。

takeIf

  • takeIf 函數需要判斷 lambda 中提供的條件運算式 (Predicate),如果是 true ,則返回接收者物件,否則返回 null。

例如:有一段程式碼,呼叫 File 建立一個檔案實例,當這個檔案可以讀寫,才能夠讀取裡面的值,否則會回傳 null。

val file = File("myFile.txt")
val fileContents = if(file.canRead && file.canWrite){
file.readText()
}else{
null
}

用 takeIf 改寫

val fileContents = File("myFile.txt")
.takeIf{ it.canRead() && it.canWrite() }
?.readText()
  • takeIf 不需要 file 的臨時變數,類似於 if ,但是他的優勢在於可以直接在物件的實例上呼叫。
  • 加上 takeIf 函數如果是 false 則會回傳 null ,所以可以用空運算子,寫出較簡潔的程式碼。

takeUnless

  • 與 takeIf 相反,當 takeUnless 裡面的值為 false 時,才會返回接收者物件,否則返回 null。

結論

Kotlin 的 Lambda 函數讓程式碼簡潔許多,再加上本篇文章所介紹的標準函數,可以減少更多臨時的變數,增加程式的可讀性。很多程式碼都有包含這些標準函數,讀完這篇之後,看到時再也不會搞不清楚狀況了。

附上標準函數的使用表:

如果這篇有幫助到你,幫我拍手鼓勵一下吧。

謝謝

Ref:

KotlinLang.org: Scope-Function

[Kotlin pearls 1] Scope Functions

--

--

Andy Lu
Andy Lu

Written by Andy Lu

Android/Flutter developer, Kotlin Expert, like to learn and share.

No responses yet