kotlin-3-作用域函数总结(run with let apply also)

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

你可能感兴趣的:(Kotlin学习笔记,kotlin,android,开发语言)