簡介 Kotlin: run, let, with, also 和 apply

Jui Yuan Liou
6 min readJun 24, 2018

--

「這麼多是要怎樣用啦。」 來源:Marvel Pictures

Kotlin 的 Standard Library 提供了幾種 Function ,有些可以處理之前提到的可為空變數。老實說如果使用到最後,會發現其實它們大多數是可以互相替換的。它們並沒有什麼特殊的特性,目的只是提升程式的語意,增加閱讀性而生。

本文先假設讀者已經知道 lambda 表達式是怎麼一回事,範例之中就不再另外解釋。[參考 Kotlin 文件的 Higher-Order Functions and Lambdas]

run

可以將 run 想像成一個獨立出來的 Scope , run 會把最後一行的東西回傳或是帶到下一個 chain。
第一個例子像是:

雖然上面有兩個 whatsMyName (其實第二個同名參數可以叫同樣名稱,是因為 Kotlin 的特性 Name shadowed) ,我們在 run 這個 scope 內又重新定義了 whatsMyName 變數,不過這不影響 scope 外面的變數。所以印出來是:

Call my name! Ajax
What's my name? Francis

run 還能將最後一行的東西回傳,或傳給下一個 chain ,也就是說能這麼寫:

/* Result */
English! Call me Softest part of heart !!

也可以這麼做:

/* Result */
WOW, This signal is from Sagittarius

with

with 一般常常作為初始化時使用, with(T) 之中的傳入值可以以 this (稱作 identifier) 在 scope 中取用,不用打出 this也沒關係。雖然, with 也會將最後一行回傳,但目前看起來大部分還是只用它來做初始化。透過 with()很明確知道是為了括弧中的變數進行設定。

但很多使用狀況變數可能是可為空的變數,如此一來 with的 scope 中就必須要宣告 「?」或「!!」來取用該物件的方法 (Method)。

T.run

什⋯ 什麼嘛,多了 T 是怎麼一回事!
這些 function 的使用方式,需要接在一個變數後面才行。像是 someVariable.run { /* do something */ },包含 T.run 下面四個 let also apply 都屬於這種 extension function。因為 run有兩種用法,這裡為了避免混淆而將 T 寫出來。

T.run 也能像 with 一樣來做初始化,而且 extension function 有個好處是可以在使用時就進行 「?」 或 「!!」 的宣告。另外,T 能夠以 this 的形式在 scope 內取用。像是上面的範例,如果用 T.run來 做初始化,就會是:

當然如果傳進來的變數是空值, T.run{} 內的程式碼就根本不會執行了。
除此之外, T.runrun 的特性完全一樣。可以將最後一行的東西回傳,或是傳給下一個 chain。參考以下範例,要根據筆電系統版本印出 Windows 的開發代號:

結果會是

Windows 8.1 codename is Blue
Windows 7 codename is Blackcomb
Windows 10 codename is Threshold

但實作上 T.run 有可能需要取用外層變數或方法,但 this 已經被變數 T 佔用。例如,取得 Activity 則要這樣處理:

有沒有其他的做法,可以讓 identifier 不是 this呢?
這就需要來介紹下一位: let

let

又或者可以寫成 T.let,也是一個 extension function。T 在 scope 內則是用 it 來存取而不是 this。也可以依照需求改成其他的名字,增加可讀性。
run 相同,會將最後一行帶到下一個 chain 或是回傳

結果會是

Open the box , and error
Open the box , and error
Open the box , and You've got a Windows install USB

let 的操作方式基本上與上述的 runT.run大至相同。依據自己的需求,也可以互相串來串去使用。需求像是希望能自訂 identifier 時,或是希望 this 可以存取到上層內容時,建議使用 let

自定義 identifier

also

也可以寫作 T.also
剩下的 alsoapply 決大部分也是使用於初始化物件。前文提到:這幾種 Standard Library Function 其實可以互相替換,選擇合適的場景使用即可。

而它們與上面的 runlet的不同之處在於: runlet 會將最後一行傳給下個 Chain 或是回傳,物件類型依最後一行而定; alsoapply 則是將「自己 (this)」回傳或傳入下個 chain

有點像是 builder pattern ,做完一次設定後又將自己回傳回去。另外, also在 scope 內可以透過 it 來存取 T本身。

結果會是

Your drink details:
sugar level is 50
ice level is 70
Customer needs plastic bag = true

話又說回來凪(Nagi) 的點餐單跟 Builder Pattern 也有異曲同工之妙呢。

apply

也可以寫作 T.apply
applyalso 有 87 分像,不同的地方是 apply 在 scope 內 T的存取方式是 this ,其他都與 also 一樣。

這裡的範例以 Fragment 生成時,需要時做的 newInstance()方法。利用 applyalso 在 Kotlin 之中如何改寫:

在 return 時,透過 apply 對 Fragment 進行加工後再回傳。

結論

這些 Standard Library 提供的 Function 其實大同小異。用的時候除了語意以外,還有什麼選擇方式?

  1. 要傳遞最後一行,還是傳遞自己?

2. 是否需要 extension function 先判斷可為空的變數?

3. Scope 內想透過 thisit 存取 T
考量可以是:是否需要存取外層的變數,identifier 是否可以依需求自由命名

先判斷 1. ,假設情境需要傳遞自己,即有 applyalso 可以選擇。再用 3. 判斷目前情境適合哪一個: apply 透過thisalso透過 it 來存取傳入變數。

如果需要傳遞最後一行,有四個選項: runT.runwithlet 。先用 2. 判斷是否需要預先判斷可為空變數。

  • 不需要先判斷,那麼就剩: runwithwith可以在 scope 內透過 this 存取傳入變數; run 沒有任何傳入變數,但可以將最後一行傳遞出去。
  • 需要先判斷,即為 T.runlet 。再用 3. 判斷哪個適合目前的情境: T.run 透過 thislet 則是透過 it 來存取傳入的變數。

Elye Project 寫的一篇:Mastering Kotlin standard functions: run, with, let, also and apply 。(簡體中文翻譯) 下方有提到明確的判斷方式,建議大家可以看看這篇。

另外,也大力推薦 朱立 Ju1ian 的 《Kotlin 的 scope function: apply, let, run..等等》。寫個更為詳細,清楚。

希望這篇文章有幫助到正在學習 Kotlin 的 Developer 。

--

--