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,以避免误写出永远不会被执行的代码。