Lambda的表示法是{ ... } ,例如:app
val func = { println() } val func = { x -> println(x) }
若函數的惟一或最後一個參數是函數類型,能夠不須要用括號圍住這個參數,這樣就能隨手寫出這樣漂亮的DSL:函數
// transaction(...)接受一個類型爲函數的參數 // Auto handle a transaction transaction { saveData() } // use(...)接受一個類型爲函數的參數 // Auto close the resource inputStream.use { consume(it) }
可是就不能像C/Java/Scala同樣用花括號把代碼組織成普通代碼塊了,而是必須調用run函數,這是Kotlin開發組的一種取捨:this
// Java String externalName; { String internalName = getInternalName(); externalName = convert(internalName); }
// Scala val externalName = { val internalName = getInternalName() convert(internalName) }
// Kotlin val externalName = run { val internalName = getInternalName() convert(internalName) }
run函數會被編譯器內聯,沒有函數調用的額外開銷,只是語法上與C家族不一致,並且有點繁瑣。code
相似run的函數有好幾個,咱們來瞧一瞧。對象
run(block: () -> R): R = block()
開發
執行block,返回block的結果R,至關於Scala的普通代碼塊
T.run(block: T.() -> R): R = block()
get
把T做爲this,執行block,返回block的結果R
T.let(block: (T) -> R): R = block(this)
input
把T做爲block的入參(it),執行block,返回block的結果R
T.apply(block: T.() -> Unit): T { block(); return this }
編譯器
把T做爲this,執行block,返回T自己
T.also(block: (T) -> Unit): T { block(this); return this }
it
把T做爲block的入參(it),執行block,返回block的結果R
with(receiver: T, block: T.() -> R): R = receiver.block()
把第一個參數做爲this傳給block。能表達這種代碼:
with(entry) { consumeKeyValue(key, value) }
當咱們想隨手轉換一個對象,能夠這麼寫:
val userDTO = user.run { UserDTO(name, password) }
或這麼寫
val userDTO = user.let { UserDTO(it.name, it.password) }
當咱們新建了一個對象,想隨手給它完成初始化,能夠這麼寫:
val user = User().apply { name = "Mr Wang" password = "&%&&**(" }
或這麼寫
val user = User().let { it.name = "Mr Wang" it.password = "&%&&**(" }
你可能以爲這只是微小的語法糖,那麼看這個nullable的例子:
// 繁瑣寫法 val userDTO = if (user != null) { UserDTO(user.name, user.password) } else { null }
// 簡潔寫法 val userDTO = user?.run { UserDTO(name, password) }
是否是高下立見?
爲了支持多種寫法,佔用了不少名字。像run這種在JDK中常見的名字也被用了,雖然有靜態檢查,但同一個名字被安上不一樣的語義仍然是一種心智負擔。何況run這個名字徹底沒有表達「轉換」的意思嘛!
使用函數以前要先想好接下來的寫法適合哪一個名字的函數,也是一種心智負擔。
減小內置函數名:去掉run和also,只保留let,apply和with。採用以下幾種函數簽名:
let(block: () -> R)
T.let(block: T.() -> R)
T.let(block: (T) -> R): R
T.apply(block: T.() -> Unit): T
T.apply(block: (T) -> Unit): T
with(receiver: T, block: T.() -> R): R
如上,with沒有變,run被併入let,also被併入apply。
let表示定義新的變量,apply表示對現有變量作一些處理,with表示以現有變量做爲scope來作一些事(包括返回新的變量)。語義很清楚。
let老是返回結果R,apply老是返回本來的T。let/apply若接受無參函數,就把T做爲this,若接受惟一參數爲T的函數,就把T做爲參數傳入,不容許隱式的it參數。
// 使用this user.apply { name = "Mr Wang" password = "&%&&**(" } // 使用it必須顯式聲明 user.apply { it -> it.name = "" it.password = "&%&&**(" }
另外一個問題,從Java轉過來的應用開發者可能會習慣性地寫一個代碼塊,忘了這其實是一個Lambda,是不會執行的。
// 不會執行 { doSomething() } // 會執行 { doSomething() }()
原則上應禁止定義未被使用的Lambda,以避免誤寫出永遠不會被執行的代碼。