官方文档 : scope function

概念

概念是最重要的,学习中最重要的任务是掌握概念。(忘记在哪里看到的了,很有道理)

scope function:

Kotlin标准库中,(1.4版本)具体是kotlin-stdlib-1.4.0.jar中的Standard.kt文件中定义的一组函数,它们都接受一个lambda函数作为参数,对一个对象调用这些方法,会构成一个临时的域(scope),在这个域中的代码可以不用变量名来指代对象。这种函数就叫 scope function。 scope function主要的作用就是对一个对象执行一块代码。

context object:

scope function创建的域中的上下文对象(用it或者this指代的对象)就是上下文对象。例如,

something?.let{
    println("something: ${it.name}") // it = something
    name = it.name // it = something
}

上面代码块中的let调用作用于something,创建的域中,it就是something,也就是“上下文对象”。

receiver:

kotlin 的官方文档中没找到专门解释这个概念的文档,StackOverflow上有个帖子很好: SO帖子

receiver的定义:Kotlin中,函数可以有一个(SO帖子里写的是多个,通过多条定义得到,但是我理解Kotlin的函数类型限制了receiver的数量,因为receiver直接写在函数类型中,且只能写一个)receiver,在函数的代码中可以直接(原文叫without qualifying,不知道怎么翻译)获取receiver类型的属性。 Kotlin的函数类型为 receiver.(parameter list) -> return type, Kotlin文档

val minus100: Int.() -> String = {minus(100).toString()}

上面的代码块中,minus100是一个函数,它的类型是Int.() -> String,所以receiver是Int,在函数代码中,直接调用了Int类型的minus方法,没有加it,或者说省略了this,这块代码仿佛就在Int类的定义里一样,所以叫extension function,扩展函数,意思是仿佛扩展了一个类。

从某种程度上,receiver将函数内代码指代上下文的方式从it变成了this,而且this可以省略。在嵌套调用lambda函数时,用this避免了内层it覆盖外层it的问题,但是增加了this到底指向谁的的问题。 例如:

val minus10it: (Int) -> Int = { it.minus(10) }
val minus10this: Int.() -> Int = { minus(10) }

val a = 10000.let {
    minus10it(it).let {
        minus10it(it)
    }
}

val b = 10000.run {
    minus10this().run {
        minus10this()
    }
}

it的版本里,出现了内外层it打架的问题,用this就没有这个问题。

let

let的定义:

public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}

let的函数签名中出现了两个类型参数 T R: T是receiver的类型,R是返回值的类型。let返回的是将上下文对象作为参数传给block得到的结果。 实际开发中,用的最多的是 ?.let这种模式,如:

resource?.let{
    // ...
} 

在不为null时进行操作。

还有一个常用场景是代替临时变量,比如有一个表达式的结果需要在后续操作中用到,可以用let来避免创建临时变量,例如:

// 临时变量
val tmp = complexAndExpensiveOperation()
doA(tmp)
doB(tmp)
// let
complexAndExpensiveOperation().let{
    doA(it)
    doB(it)
}

run

run的定义:

public inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}

let的区别:run中block里用this指代上下文对象,let中用it指代。 run还有另一个签名:

public inline fun <R> run(block: () -> R): R {
    return block()
}

这个版本没有上下文变量,没怎么用过。

apply

apply的定义:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

执行一个函数,并返回上下文对象。纯粹为了函数的副作用。

also

also的定义:

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

apply的区别是block中用it指代上下文对象。 刚学Kotlin的时候,有个用also交换两数的例子:

var a = 123
var b = 321

a = b.also { b = a }
println(a) // 321
println(b) // 123

在执行also的传入参数block时,上下文对象被拷贝了,并且在{b=a}这个代码块中,并没有改变上下文对象it

var a = 123
var b = 321

a = b.also {
    println("b=$b, it=$it") // b=321, it=321
    b = a
    println("b=$b, it=$it") // b=123, it=321
}
println(a) // 321
println(b) // 123

b先是被拷贝到also中的this,然后作为block里的it,执行完block后,also返回了this,所以,b.also{b=a}的值是一开始的b,而现在的b在执行block的时候变成了一开始的a

with

with的定义:

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}

run的第一种定义比较像,都是有一个receiver,一个带receiver的lambda,不同的是receiver的位置,runreceiver.run(block)withwith(receiver, block),Kotlin 语言规范中说明了两种类型的区别:Function typesrunFTR(RT,A1)->R,withFT(RT, A1)->R,或者说,runT.(T.()->R)->Rwith(T,T.()->R)->R。这两种定义在重载时有区别。run可以作为很多类型的扩展函数,with并不是扩展函数。