iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
Mobile Development

【Kotlin Notes And JetPack】Build an App系列 第 3

Day 3.【Standard Library】Scope Functions

  • 分享至 

  • xImage
  •  

這篇要來聊聊 Kotlin 在 standard library 中所提供的 Scope Functions,至於什麼是 Scope Functions 以及該如何使用,我們就來一一探討吧!以下如有解釋不清或是描述錯誤的地方還請大家多多指教:

什麼?

Scope Functions 的目標很單一,透過一個 block 包覆相同物件所執行的內容,並且執行這個 block 裡所有的動作,這些方法有著 lambda 表示式,針對指定的物件形成一個臨時的執行區,在這個區域裡不用再 call 物件本身就可以對物件執行動作,而 Scope Functions 總共有五個,以下會分別解釋這五個的差異:letrunwithapply, and also

| 什麼情境下會遇到

在什麼樣子的開發情境下會需要使用到呢?

val luffy = Character("Luffy", 20, "SKY PIEA")
luffy.moveTo("Wano Country")
luffy.incrementAge()

當同一個物件需要執行超過一個以上的動作時,為了讓程式碼看起來更簡潔,避免一直寫重複的名稱,這時候就可以依情境使用那 5 個 function。

Character("Luffy", 20, "SKY PIEA").apply {
    moveTo("Wano Country")
    incrementAge()
}

| Let it Run this With literal, it Also Apply this

這是我看到某篇文章寫得口訣,我補上 with 在這段話中,讓 Scope Functions 使用方式搭配官方的表格更清晰。

Function Object reference Return value Is extension function
let it Lambda result Yes
run this Lambda result Yes
run - Lambda result No: called without the context object
with this Lambda result No: takes the context object as an argument.
apply this Context object Yes
also it Context object Yes
  • letrunwith 返回值都是最後一行
  • apply, also 返回值是物件本身

? Object reference : An object reference is information on how to find a particular object

那針對使用情境官方有提供了一些方向做參考,但也沒有說這些情境下一定要使用特定的 function,主要還是依目的跟易讀為主,使用的人可以根據團隊或是專案立訂使用的情境:

  • Executing a lambda on non-null objects: let
  • Introducing an expression as a variable in local scope: let
  • Object configuration: apply
  • Object configuration and computing the result: run
  • Running statements where an expression is required: non-extension run
  • Additional effects: also
  • Grouping function calls on an object: with

雖然他可以讓程式碼變得更簡潔,但要避免過度使用他而呈現巢狀,這會很容易混淆目前的開發內容是回傳 this 還是 it

如何?

我們要如何實作這 5 個 function 呢?以下我們有一個 Character 的 data class(有標註 // system display 都是 IDE 會顯示的,是為了凸顯 it 跟 this 所以有打出來)。

data class Character (
	var name: String,
	var age: Int,
	var country: String
) {
    fun moveTo(newCity: String) { country = newCity }
    fun incrementAge() { age++ }
}

| LET

let 的 Object reference 是 it ,返回值是最後一行的 value:

val luffy = Character("Luffy", 20, "SKY PIEA").let { it: Character // system display
    it.moveTo("Wano Country")
    it.incrementAge()
	it.age
}
print(luffy) // get 21

Object reference 的 it 也可以自行命名:

Character("Luffy", 20, "SKY PIEA").let { child ->
    child.moveTo("Wano Country")
    child.incrementAge()
	child.age
}

| RUN

run 的 Object reference 是 this,返回值是最後一行的 value,在 block 中可省略 this 直接呼叫物件執行動作:

val luffy = Character("Luffy", 20, "SKY PIEA").run { this: Character // system display
    moveTo("Wano Country")
    incrementAge()
	age
}
print(luffy) // get 21

而 run 除了針對物件本身形成一個 scope 之外還有另一個用法,當我們上述的狀況有可能為 null 的情況下想要針對 null 與 non-null 做不同的行為:

val person: Character ?= null

// 一般方式
if (person.isNullOrEmpty()) {
	// do something
} else {
	// do something
}

// scope function
person?.let {
	// only not null object can access
} ?: run {
	// do something when object is null
}

這裡補充一個意外的小知識,原本只單純想補充 ?: 這個的意思,意外查到他命名的來歷:

?: 稱作 Elvis Operator,如果第一操作數求值為真則返回其值,否則返回第二操作數的值
埃爾維斯運算符得名於它的通常表示法 ?:,相似於埃爾維斯·普雷斯利
(即「貓王」)的顏文字側臉的額發,或者其他角度看相遇於他的得意的笑臉。

最後用簡單的例子來說明他的用法及語意:

val result = valueA ?: valueB
// 當 valueA 不等於 null 的時候 result = valueA 
// 當 valueA 等於 null 的時候 result = valueB

以前面 person 來說,當 person 是 null 的時候就執行 run scope 的事情。

| WITH

with 的 Object reference 是 this,返回值是最後一行的 value:

val luffy = Character("Luffy", 20, "SKY PIEA")
val age = with(luffy) { this: Character // system display
    moveTo("Wano Country")
    incrementAge()
    age
}
print(age) // get 21

| ALSO

also 的 Object reference 是 it,返回值是自己:

val luffy = Character("Luffy", 20, "SKY PIEA").also { it: Character // system display
    it.moveTo("Wano Country")
    it.incrementAge()
	it.age // 雖然最後一行是 age 但 print 出來會是物件本身
}
print(luffy) // get Character(name=Alice, age=21, city=London)

let 依樣 it 可以自行改名

val luffy = Character("Luffy", 20, "SKY PIEA").also { student ->
    student.moveTo("Wano Country")
    student.incrementAge()
	student.age
}
print(luffy) // get Character(name=Alice, age=21, city=London)

| APPLY

apply 的 Object reference 是 this,返回值是自己,在 block 中可省略 this 直接呼叫物件執行動作:

val luffy = Character("Luffy", 20, "SKY PIEA").apply { this: Character // system display
    moveTo("Wano Country")
    incrementAge()
	age // 雖然最後一行是 age 但 print 出來會是物件本身
}
print(luffy) // get Character(name=Alice, age=21, city=London)

Reference

Official Kotlin
口訣來源
Elvis Operator


上一篇
Day 2. Kotlin 的特性
下一篇
Day 4.【Functions】Lambdas
系列文
【Kotlin Notes And JetPack】Build an App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Daniel Kao
iT邦新手 4 級 ‧ 2023-01-03 18:19:24

data class Character 有錯,沒有 class member city 存在;第一個函式無法 compile。

另外,關於 with 的用法也有點錯誤。
下面印出來的應該要是 Character object,而不是 21 才對

val luffy = Character("Luffy", 20, "SKY PIEA")
with(luffy) { 
    moveTo("Wano Country")
    incrementAge()
	age
}
print(luffy)

感謝指正~~~已更正,目前我在 with 使用情境比較少,如果有其他使用情境也請大大們提點,已更正成以下寫法:

val luffy = Character("Luffy", 20, "SKY PIEA")
val age = with(luffy) { this: Character // system display
    moveTo("Wano Country")
    incrementAge()
    age
}
print(age) // get 21

這邊也補上 with 的 source code

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

我要留言

立即登入留言