情况
Lambda的表示法是{ ... } ,例如:
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开发组的一种取舍:
// 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家族不一致,而且有点繁琐。
类似run的函数有好几个,我们来瞧一瞧。
run(block: () -> R): R = block()
执行block,返回block的结果R,相当于Scala的普通代码块
T.run(block: T.() -> R): R = block()
把T作为this,执行block,返回block的结果R
T.let(block: (T) -> R): R = block(this)
把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 }
把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,以免误写出永远不会被执行的代码。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。