kotlin中有一些标准库函数-run、with、let、apply以及also,对初步学习过程,总是傻傻分不清楚,特做此记录。
上面的标准库函数,依次可以分为run{}、T.run()、with()、T.let()、T.apply()、T.also(),它们通通为调用者提供内部作用域,所以,它们又叫作用域函数。
记录这些作用域函数之前,我们先从简地学习一下{}
{}:在kotlin中只用{}的话是作为函数体的作用域。
fun main(args: Array) {
val result = {
println("hello this is {block}")
1
"MonkeyKing"
}
println(result) //Function0
println(result()) //hello this is {block}
//MonkeyKing
}
这么看的话,kotlin把只有{}的整体当作了一个带返回值的函数体,返回值为最后一行代码的执行结果
而我们知道,一般的函数体定义为 val result = fun(){}
fun main(args: Array) {
val result =fun() {
println("hello this is {block}")
1
"MonkeyKing"
}
println(result) //Function0
println(result()) //hello this is {block}
//kotlin.Unit
}
通过比较可以知道,加了函数关键字fun定义后,也就必须规定函数返回值类型(不规定则为Unit)
而且,函数有一个很明显的特点,不调用函数则函数体内部不会被执行。
run():带返回值的代码块,而且代码块会按顺序执行,返回结果为最后一行的执行结果,否则为Unit。
fun main(args: Array) {
println("step1")
val result = run {
1
println("step3")
"MonkeyKing"
}
println("step2")
println(result.javaClass)
println(result)
// println(result())//不是函数直接报错
}
log:
step1
step3
step2
class java.lang.String
MonkeyKing
源码分析
/**
* Calls the specified function [block] and returns its result.
* 调用具体的方法[block]并返回结果
*/
@kotlin.internal.InlineOnly
public inline fun run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)//面向编译器,高速编译器我要执行一遍lamdba表达式
}
return block()//执行lamdba表达式,并返回结果
}
T.run():把自身传入lamdba表达式中,同时返回最后一行执行结果。
fun main(args: Array) {
val name = StringBuffer("MonkeyKing")
val result = name.run {
println(this) //MonkeyKing this就是name传进来的别名,作用等同于name,修改了内容name也会同步修改
this.append(" SunWuKong")
println(this) //MonkeyKing SunWuKong
"齐天大圣"
}
println("result=$result") //齐天大圣
println("name=$name") //MonkeyKing SunWuKong
}
源码分析:和run{}相差不大,也是告诉编译器执行一遍,返回执行结果
@kotlin.internal.InlineOnly
public inline fun T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
with(T):想比较其他作用域函数,with的显著特点是它是内联函数,但不是拓展函数,可以把with当作一个稍微特殊的函数,函数名为with,传入的参数在函数体内部的别名为this,同时返回值为lamdba表达式的结果。
fun main(args: Array) {
var name = "Monkey king"
val result = with(name) {
println("this=$this") //this=Monkey king
"hellokotlin"
}
println("name=$name") //name=Monkey king
println("result=$result") //result=hellokotlin
}
源码分析:不是拓展函数,只是内联
@kotlin.internal.InlineOnly
public inline fun with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
综合比较,T.run()和with(T)功能很相似,除了run是T的拓展函数而with不是外,其他基本一致,但有的时候更倾向于使用T.run()
fun main(args: Array) {
var name = "MonkeyKing"
name.run {
println(this)
}
with(name) {
println(this)
}
println("当T可能为null时")
var myName: String? = null
myName?.run {//外部拦截 this:String
println("我不可能为null Myname=$this")
println(this.length)
println(this.endsWith("a"))
}
with(myName) {//会作为传进来 this:String?
println(this)
println(this?.length)
println(this?.endsWith("a"))
}
}
这样一比较,run()就优雅多了。
T.let();调用let函数,同时将自身传进去,用it来标示,返回为lamdba的执行结果,let和run极其相似,一个的别名是it,一个是this,run中的T更像是在T对象的上下文里面,所以有些方法可以省略,let中的T更倾向于对象的外部处理,比如arraylist的add操作,同时let还能更改别名,而run只能用this。
fun main(args: Array) {
val lists = ArrayList()
var result = lists.let { //it:Array
it.add(23)
it.add(22)
it.add(21)
it.add(20)
"MonkeyKing"
}
lists.let { lllist ->//lllist:Array
lllist.add(19)
lllist.add(18)
lllist.add(17)
}
println("size=${lists.size}") //7
println("result=$result") //MonkeyKing
}
源码分析 T的拓展函数 只执行一遍 把自身传进去
@kotlin.internal.InlineOnly
public inline fun T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
上面的作用域函数只返回lamdba表达式的最后一句的执行结果,后面的apply和also则是返回T本身(A开头的是返回本身,这就很好记了)。apply和also因为是返回调用者T本身,所以在链式调用中就极其的方便。
T.apply():将自身传入表达式中,以this作为标示,同时将T本身返回。
fun main(args: Array) {
val lists = ArrayList()
var result = lists.apply {
this.add(23)
this.add(22)
this.add(21)
this.add(20)
"MonkeyKing"
}
println("size=${lists.size}") //4
println("result=$result") //result=[23, 22, 21, 20]
println(result == lists) //true
}
源码分析:
@kotlin.internal.InlineOnly
public inline fun T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block() //执行函数表达式
return this //本身
}
T.also():同样的,also和apply的区别在于apply传进去的this,而also默认传进去的是it(可人为修改)
fun main(args: Array) {
val lists = ArrayList()
var result = lists.also {
it.add(23)
it.add(22)
it.add(21)
it.add(20)
"MonkeyKing"
}
lists.also { itt ->
itt.add(10)
itt.add(11)
itt.add(12)
}
println("size=${lists.size}") //size=7
println("result=$result") //result=[23, 22, 21, 20, 10, 11, 12]
println(result == lists) //true
}
源码分析
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this) //传入调用者本身
return this //返回调用者本身
}
作用域函数大体介绍完毕,总结一下,最终还是得多使用才能熟悉。
函数 | 是否拓展函数 | 返回值 | 对象引入 |
run() | 否 | lamdba执行结果 | 无 |
T.run() | 是 | lamdba执行结果 | this |
T.let() | 是 | lamdba执行结果 | it |
with(T) | 否 | lamdba执行结果 | this |
T.apply() | 是 | T | this |
T.also() | 是 | T | it |