1. 程式人生 > 程式設計 >Kotlin作用域函式之間的區別和使用場景詳解

Kotlin作用域函式之間的區別和使用場景詳解

作用域函式

Kotlin 的作用域函式有五種:let、run、with、apply 以及 also。

這些函式基本上做了同樣的事情:在一個物件上執行一個程式碼塊。

下面是作用域函式的典型用法:

val adam = Person("Adam").apply { 
 age = 20
 city = "London"
}
println(adam)

如果不使用 apply 來實現,每次給新建立的物件屬性賦值時就必須重複其名稱。

val adam = Person("Adam")
adam.age = 20
adam.city = "London"
println(adam)

作用域函式沒有引入任何新的技術,但是它們可以使你的程式碼更加簡潔易讀。

事實上,同樣的功能可能多個作用域函式都能實現,但我們應該根據不同的場景和需求來使用合適的作用域函式,以求更優雅的實現。

如果想直接檢視作用域函式之間的區別與使用場景歸納表,請點選這裡。

下面我們將詳細描述這些作用域函式的區別及約定用法。

區別

作用域函式之間有兩個主要區別:

  • 引用上下文物件的方式
  • 返回值

引用上下文物件的方式:this 還是 it

在作用域函式的 lambda 表示式裡,上下文物件可以不使用其實際名稱而是使用一個更簡短的引用(this 或 it)來訪問。
作用域函式引用上下文物件有兩種方式:

  • 作為 lambda 表示式的接收者(this): run、with、apply
  • 作為 lambda 表示式的引數(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 表示式的接收者

run、with 以及 apply 將上下文物件作為 lambda 表示式接收者,通過關鍵字 this 引用上下文物件。可以省略 this,使程式碼更簡短。

使用場景:主要對上下文物件的成員進行操作(訪問屬性或呼叫函式)。

val adam = Person("Adam").apply { 
 age = 20  // 和 this.age = 20 或者 adam.age = 20 一樣
 city = "London"
}
println(adam)

作為 lambda 表示式的引數

let 及 also 將上下文物件作為 lambda 表示式引數。如果沒有指定引數名,物件可以用隱式預設名稱 it 訪問。it 比 this 簡短,帶有 it 的表示式通常更容易閱讀。然而,當呼叫物件函式或屬性時,不能像 this 這樣隱式地訪問物件。

使用場景:主要對上下文物件進行操作,作為引數使用。

fun getRandomInt(): Int {
 return Random.nextInt(100).also {
 writeToLog("getRandomInt() generated value $it")
 }
}
val i = getRandomInt()

此外,當將上下文物件作為引數傳遞時,可以為上下文物件指定在作用域內的自定義名稱(為了提高程式碼的可讀性)。

fun getRandomInt(): Int {
 return Random.nextInt(100).also { value ->
 writeToLog("getRandomInt() generated value $value")
 }
}
val i = getRandomInt()

返回值

根據返回結果,作用域函式可以分為以下兩類:

  • 返回上下文物件:apply、also
  • 返回 lambda 表示式結果:let、run、with

可以根據在程式碼中的後續操作來選擇適當的函式。

返回上下文物件

apply 及 also 的返回值是上下文物件本身。因此,它們可以作為輔助步驟包含在呼叫鏈中:你可以繼續在同一個物件上進行鏈式函式呼叫。

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()

它們還可以用在返回上下文物件的函式的 return 語句中。

fun getRandomInt(): Int {
 return Random.nextInt(100).also {
 writeToLog("getRandomInt() generated value $it")
 }
}

val i = getRandomInt()

返回lambda表示式結果

let、run 及 with 返回 lambda 表示式的結果。所以,在需要使用其結果給一個變數賦值,或者在需要對其結果進行鏈式操作等情況下,可以使用它們。

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.")

此外,還可以忽略返回值,僅使用作用域函式為變數建立一個臨時作用域。

val numbers = mutableListOf("one","three")
with(numbers) {
 val firstItem = first()
 val lastItem = last() 
 println("First item: $firstItem,last item: $lastItem")
}

約定用法

let

上下文物件 作為 lambda 表示式的 引數(it)來訪問。 返回值 是 lambda 表示式的結果。

let 可用於在呼叫鏈的結果上呼叫一個或多個函式。例如,以下程式碼列印對集合的兩個操作的結果:

val numbers = mutableListOf("one","three","four","five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)

使用 let,可以寫成這樣:

val numbers = mutableListOf("one","five")
numbers.map { it.length }.filter { it > 3 }.let { 
 println(it)
 // 如果需要可以呼叫更多函式
} 

若程式碼塊僅包含以 it 作為引數的單個函式,則可以使用方法引用(::)代替 lambda 表示式:

val numbers = mutableListOf("one","five")
numbers.map { it.length }.filter { it > 3 }.let(::println)

let 經常用於 僅使用非空值執行程式碼塊。如需對非空物件執行操作,可對其使用安全呼叫操作符 ?. 並呼叫 let 在 lambda 表示式中執行操作。

val str: String? = "Hello" 
//processNonNullString(str) // 編譯錯誤:str 可能為空
val length = str?.let { 
 println("let() called on $it")
 processNonNullString(it) // 編譯通過:'it' 在 '?.let { }' 中必不為空
 it.length
}

使用 let 的另一種情況是引入作用域受限的區域性變數以提高程式碼的可讀性。如需為上下文物件定義一個新變數,可提供其名稱作為 lambda 表示式引數來替預設的 it。

val numbers = listOf("one","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

一個非擴充套件函式:上下文物件作為引數傳遞,但是在 lambda 表示式內部,它可以作為接收者(this)使用。 返回值是lambda 表示式結果。

建議使用 with 來呼叫上下文物件上的函式,而不使用 lambda 表示式結果。 在程式碼中,with 可以理解為“對於這個物件,執行以下操作。”

val numbers = mutableListOf("one","three")
with(numbers) {
 println("'with' is called with argument $this")
 println("It contains $size elements")
}

with 的另一個使用場景是引入一個輔助物件,其屬性或函式將用於計算一個值。

val numbers = mutableListOf("one","three")
val firstAndLast = with(numbers) {
 "The first element is ${first()}," +
 " the last element is ${last()}"
}
println(firstAndLast)

run

上下文物件 作為 接收者(this)來訪問。 返回值 是 lambda 表示式結果。

run 和 with 做同樣的事情,但是呼叫方式和 let 一樣——作為上下文物件的擴充套件函式.

當 lambda 表示式同時包含物件初始化和返回值的計算時,run 很有用。

val service = MultiportService("https://example.kotlinlang.org",80)
val result = service.run {
 port = 8080
 query(prepareRequest() + " to port $port")
}
// 同樣的程式碼如果用 let() 函式來寫:
val letResult = service.let {
 it.port = 8080
 it.query(it.prepareRequest() + " to port ${it.port}")
}

除了在接收者物件上呼叫 run 之外,還可以將其用作非擴充套件函式。 非擴充套件 run 可以使你在需要表示式的地方執行一個由多個語句組成的塊。

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

上下文物件 作為 接收者(this)來訪問。返回值 是上下文物件本身。

對於不返回值且主要在接收者(this)物件的成員上執行的程式碼塊使用 apply。apply 的常見情況是物件配置。這樣的呼叫可以理解為“將以下賦值操作應用於物件”。

val adam = Person("Adam").apply {
 age = 32
 city = "London" 
}
println(adam)

將接收者作為返回值,可以輕鬆地將 apply 包含到呼叫鏈中以進行更復雜的處理。

also

上下文物件作為 lambda 表示式的引數(it)來訪問。 返回值 是上下文物件本身。

also 對於執行一些將上下文物件作為引數的操作很有用。 對於需要引用物件而不是其屬性與函式的操作,或者不想遮蔽來自外部作用域的 this 引用時,請使用 also。

當在程式碼中看到 also 時,可以將其理解為“並且用該物件執行以下操作”。

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

總結

下表總結了Kotlin作用域函式的主要區別與使用場景:

函式 物件引用 返回值 是否是擴充套件函式 使用場景
let it Lambda 表示式結果 1. 對一個非空物件執行 lambda 表示式
2. 將表示式作為變數引入為區域性作用域中
run this Lambda 表示式結果 物件配置並且計算結果
run - Lambda 表示式結果 不是:呼叫無需上下文物件 在需要表示式的地方執行語句
with this Lambda 表示式結果 不是:把上下文物件當做引數 一個物件的一組函式呼叫
apply this 上下文物件 物件配置
also it 上下文物件 附加效果

參考資料

Kotlin語言中文網

好了,到此這篇關於Kotlin作用域函式之間的區別和使用場景的文章就介紹到這了,更多相關Kotlin作用域函式區別與使用場景內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!