kotlin之let、with、run、apply、also

總結(jié)

let、with、run、apply、also 這幾個(gè)的作用就是在一個(gè)對(duì)象的上下文中執(zhí)行一段代碼。當(dāng)我們使用 lambda 表達(dá)式在一個(gè)對(duì)象上調(diào)用這樣的一個(gè)函數(shù)時(shí),它就形成了一個(gè)暫時(shí)的域,在這個(gè)域中,可以在不使用變量名的情況下獲取到對(duì)象,因此,這些函數(shù)也被稱為作用域函數(shù)(Scope functions)

run、with 和 apply 的上下文對(duì)象是 this ,可以省略掉 this 單詞,因此主要用于操作對(duì)象成員(例如調(diào)用對(duì)象的方法或使用其屬性)的時(shí)候

let 和 also 的上下文對(duì)象是 it , 適用于將此對(duì)象作為方法調(diào)用參數(shù)傳入

apply 和 also 返回上下文對(duì)象,可用于鏈?zhǔn)秸{(diào)用返回上下文對(duì)象的函數(shù)的返回語(yǔ)句

let、run 和 with 返回 lambda 結(jié)果,可以在將結(jié)果分配給變量時(shí)使用

方法 上下文對(duì)象 返回值 擴(kuò)展函數(shù) 使用場(chǎng)景
let it lambda 結(jié)果 在非空對(duì)象上執(zhí)行一個(gè) lambda或者在局部域中引入一個(gè)變量
with this lambda 結(jié)果 在一個(gè)對(duì)象上組合函數(shù)調(diào)用
run this lambda 結(jié)果 對(duì)象配置并計(jì)算結(jié)果
apply this 上下文對(duì)象 對(duì)象配置
also it 上下文對(duì)象 額外的效果
非擴(kuò)展函數(shù) run 無(wú) lambda 結(jié)果 在需要表達(dá)式的地方運(yùn)行語(yǔ)句

表格里的上下文對(duì)象中,this 是可以省略的, it 是隱含的默認(rèn)參數(shù)名,可以顯式地指定為其他名字

let

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

可以看到第一句代碼是 Contract ,這就需要引出 Contract 的內(nèi)容

Contract 示例

fun String?.isNull(): Boolean {
    return this == null
}

fun test() {
    var str: String? = null

    if (str.isNull()) {
        str = "kotlin contract"
    }

    println(str.length)
}

如上的代碼中,先為 String? 定義一個(gè)擴(kuò)展函數(shù) isNull(),用于判斷是否為 null,在 test 函數(shù)中,聲明一個(gè) String? 類型的 str ,使用 isNull() 判斷,如果其為 null ,就為其賦值,這樣,在下面調(diào)用 str.length 的時(shí)候 str 就一定不會(huì)為 null ,但實(shí)際上,這里還是會(huì)編譯報(bào)錯(cuò):

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

這是因?yàn)榫幾g器無(wú)法分析每個(gè)函數(shù),不能得到 str 不為 null 的結(jié)果,也就無(wú)法將 String? 智能轉(zhuǎn)換為 String ,Contract 就可以解決問(wèn)題,它可以向編譯器通知函數(shù)行為,上面的代碼修改為如下,就不會(huì)報(bào)錯(cuò)了

@ExperimentalContracts
fun String?.isNull(): Boolean {
    //下面是添加的內(nèi)容
    contract {
        returns(false) implies (this@isNull != null)
    }
    //上面是添加的內(nèi)容
    return this == null
}

@ExperimentalContracts
fun test() {
    var str: String? = null

    if (str.isNull()) {
        str = "kotlin contract"
    }

    println(str.length)
}

上面代碼中添加內(nèi)容是告訴編譯器:如果返回值為 false ,那么 this(函數(shù)的接收者)不為 null

Contract 的概念

Contract 是一種向編譯器通知函數(shù)行為的方法,有以下特點(diǎn):

  1. 只能在 top-level 函數(shù)體內(nèi)使用 Contract
  2. Contract 所調(diào)用的聲明必須是函數(shù)體內(nèi)第一條語(yǔ)句
  3. Kotlin 編譯器并不會(huì)驗(yàn)證 Contract,因此必須編寫正確合理的 Contract
  4. 內(nèi)聯(lián)化的函數(shù)(也需要是 top-level 層級(jí)的函數(shù))支持使用 Contract

Contract 的分類

Returns Contracts

表示當(dāng) return 的返回值是某個(gè)值(例如true、false、null)時(shí),implies 后面的條件成立,有以下幾種形式:

形式 說(shuō)明
returns(value: Any?) implies 條件 如果函數(shù)返回值為 value,條件成立
returns() implies 條件 如果函數(shù)能夠正常返回,且沒(méi)有拋出異常,條件成立
returnsNotNull implies 條件 如果函數(shù)返回非 null 值,條件成立

CallsInPlace Contracts

CallsInPlace Contracts 允許開(kāi)發(fā)者對(duì)調(diào)用的 lambda 表達(dá)式進(jìn)行頻率上的約束,只能在 inline 函數(shù)中調(diào)用

前面的高階函數(shù) let 就是一個(gè) CallsInPlace Contracts

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

contract() 中的 callsInPlace 會(huì)告訴編譯器,lambda 表達(dá)式 block 在 let 函數(shù)內(nèi)只會(huì)執(zhí)行一次

callsInPlace() 中的 InvocationKind 是一個(gè)枚舉類,包含如下的枚舉值

枚舉值 說(shuō)明
AT_MOST_ONCE 函數(shù)參數(shù)調(diào)用次數(shù) <= 1
EXACTLY_ONCE 函數(shù)參數(shù)調(diào)用次數(shù) == 1
AT_LEAST_ONCE 函數(shù)參數(shù)調(diào)用次數(shù) >= 1
UNKNOWN 函數(shù)參數(shù)調(diào)用次數(shù) 不限制

以上 contract 內(nèi)容就結(jié)束了,下面看 let 函數(shù)的實(shí)現(xiàn),首先要明白兩點(diǎn):

  1. 在擴(kuò)展函數(shù)內(nèi)部,你可以像成員函數(shù)那樣使用 this 來(lái)引用接收者對(duì)象
  2. 當(dāng) lambda 表達(dá)式只有一個(gè)參數(shù),可以用 it 關(guān)鍵字來(lái)引用唯一的實(shí)參

源碼

上面的源代碼里可以看到:

let 函數(shù)是類型 T 的擴(kuò)展函數(shù),返回類型為 R,只有一個(gè)參數(shù),即 block,類型為 (T) -> R,指代 參數(shù)為 T ,返回值為 R 的函數(shù),因此上下文對(duì)象為 it ,指代 block 的唯一參數(shù) T 。返回值為 block(this) ,是調(diào)用 block 指代的函數(shù),并返回 block 函數(shù)的返回值,也就是返回 lambda 的結(jié)果

示例

在非空對(duì)象上執(zhí)行一個(gè) lambda

val str: String? = "Hello"   
//processNonNullString(str)       // compilation error: str can be null
val length = str?.let { 
    println("let() called on $it")        
    processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
    it.length
}

為了提高可讀性,在局部域中引入一個(gè)變量

val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first().let { firstItem ->
    println("The first item of the list is '$firstItem'")
    if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
}.uppercase()
println("First item after modifications: '$modifiedFirstItem'")

with

源碼

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with 函數(shù)不是擴(kuò)展函數(shù),而是將類型 T 的一個(gè)對(duì)象 receiver 作為參數(shù)傳入 with 函數(shù),同時(shí)傳入 block 參數(shù),block 的類型為 T.() -> R ,即 類型 T 的一個(gè)無(wú)參且返回值為類型 R 的擴(kuò)展函數(shù),而 block 函數(shù)的調(diào)用者就是前面?zhèn)魅氲?receiver 參數(shù),因此上下文對(duì)象為 this ,指代擴(kuò)展函數(shù)的接收者 receiver ,返回值為 receiver.block() ,是調(diào)用 block 指代的函數(shù),并返回 block 函數(shù)的返回值,也就是返回 lambda 的結(jié)果

示例

推薦使用情況:調(diào)用對(duì)象的方法和屬性,但不返回結(jié)果。意味著 “with this object, do the following

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

使用對(duì)象的屬性或方法計(jì)算出一個(gè)結(jié)果

val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
    "The first element is ${first()}," +
    " the last element is ${last()}"
}
println(firstAndLast)

run

run 有兩個(gè),一個(gè)是擴(kuò)展函數(shù) run ,一個(gè)是非擴(kuò)展函數(shù) run

源碼

擴(kuò)展函數(shù) run

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

run 函數(shù)是類型 T 的一個(gè)擴(kuò)展函數(shù),返回值類型為 R,只有一個(gè)參數(shù),即 block,類型為 T.() -> R ,即 T 的一個(gè)無(wú)參且返回值類型為 R 的擴(kuò)展函數(shù),因此上下文對(duì)象為 this ,指代調(diào)用 block 擴(kuò)展函數(shù)的接收者,也就是調(diào)用 run 函數(shù)的對(duì)象,返回值為 block() ,是調(diào)用 block 指代的函數(shù),并返回 block 函數(shù)的返回值,也就是返回 lambda 的結(jié)果

非擴(kuò)展函數(shù) run

@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

run 函數(shù)不是擴(kuò)展函數(shù),返回類型為 R , run 函數(shù)的唯一參數(shù) block 的類型為 () -> R ,即 一個(gè)無(wú)參且返回類型為 R 的函數(shù)類型,因此 沒(méi)有上下文對(duì)象,返回值為 block() ,是調(diào)用 block 指代的函數(shù),并返回 block 函數(shù)的返回值,也就是返回 lambda 的結(jié)果

示例

擴(kuò)展函數(shù) run

同時(shí)包含對(duì)象初始化和返回值的計(jì)算

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}

非擴(kuò)展函數(shù) run

需要返回值的情況下執(zhí)行由多個(gè)語(yǔ)句組成的塊

val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
    println(match.value)
}

apply

源碼

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

apply 函數(shù)是類型 T 的一個(gè)擴(kuò)展函數(shù),且其返回值類型為 T 類型,唯一的參數(shù) block 的類型為 T.() -> Unit,即 T 的一個(gè)無(wú)參無(wú)返回值擴(kuò)展函數(shù),因此上下文對(duì)象為 this ,指代調(diào)用 block 擴(kuò)展函數(shù)的接收者,也就是調(diào)用 apply 函數(shù)的對(duì)象 ,返回值為 this,也就是調(diào)用者,即返回上下文對(duì)象

示例

對(duì)象配置。意味著 “apply the following assignments to the object

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

also

源碼

public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

also 函數(shù)是類型 T 的一個(gè)擴(kuò)展函數(shù),且其返回值類型為 T 類型,block 參數(shù)的類型為 (T) -> Unit,即 參數(shù)為 T 且無(wú)返回值的函數(shù),因此上下文對(duì)象為 it ,指代 block 的唯一參數(shù) T ,返回值為 this,也就是調(diào)用者,即返回上下文對(duì)象

示例

將上下文作為參數(shù)。意味著 "and also do the following with the object"

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容