溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Kotlin作用域函數(shù)怎么應(yīng)用

發(fā)布時(shí)間:2022-08-24 16:36:19 來源:億速云 閱讀:142 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹了Kotlin作用域函數(shù)怎么應(yīng)用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Kotlin作用域函數(shù)怎么應(yīng)用文章都會(huì)有所收獲,下面我們一起來看看吧。

1.前置知識(shí)

在Kotlin中,函數(shù)是一等公民,它也是有自己的類型的。比如()->Unit,函數(shù)類型是可以被存儲(chǔ)在變量中的。

Kotlin中的函數(shù)類型形如:()->Unit、(Int,Int)->StringInt.(String)->String等。它們有參數(shù)和返回值。

最后一個(gè)Int.(String)->String比較奇怪,它表示函數(shù)類型可以有一個(gè)額外的接收者類型。這里表示可以在Int對(duì)象上調(diào)用一個(gè)String類型參數(shù)并返回一個(gè)String類型的函數(shù)。

val test: Int.(String) -> String = { param ->
    "$this param=$param"
}
println(1.test("2"))
println(test(1, "2"))

如果我們把Int.(String) -> String類型定義成變量,并給它賦值,后面的Lambda的參數(shù)param就是傳入的String類型,最后返回值也是String,而在這個(gè)Lambda中用this表示前面的接收者類型Int的對(duì)象,有點(diǎn)像擴(kuò)展函數(shù),可以在函數(shù)內(nèi)部通過this來訪問一些成員變量、成員方法什么的。可以把這種帶接收者的函數(shù)類型,看成是成員方法。

因?yàn)樗穆暶鞣绞接悬c(diǎn)像擴(kuò)展函數(shù),所以我們可以使用1.test("2")來調(diào)用test這個(gè)函數(shù)類型,它其實(shí)編譯之后最終是將1這個(gè)Int作為參數(shù)傳進(jìn)去的。所以后面的test(1, "2")這種調(diào)用方式也是OK的。

有了上面的知識(shí)補(bǔ)充,咱們再來看Kotlin的標(biāo)準(zhǔn)庫函數(shù)apply

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
  • 首先apply是一個(gè)擴(kuò)展函數(shù),其次是帶泛型的,意味著任何對(duì)象都可以調(diào)用apply函數(shù)。

  • 接著它的參數(shù)是帶接收者的函數(shù)類型,接收者是T,那么調(diào)用block()就像是調(diào)用了T對(duì)象里面的一個(gè)成員函數(shù)一樣,在block函數(shù)內(nèi)部可以使用this來對(duì)公開的成員變量和公開的成員函數(shù)進(jìn)行訪問

  • 返回值:就是T,哪個(gè)對(duì)象調(diào)用的該擴(kuò)展函數(shù)就返回哪個(gè)對(duì)象

2.使用

作用域函數(shù)是Kotlin內(nèi)置的,可對(duì)數(shù)據(jù)進(jìn)行操作轉(zhuǎn)換等。

先來看個(gè)demo,let和run

data class User(val name: String)
fun main() {

    val user = User("云天明")
    val letResult = user.let { param ->
        "let 輸出點(diǎn)東西 ${param.name}"
    }
    println(letResult)
    val runResult = user.run {  //this:User
        "run 輸出點(diǎn)東西 ${this.name}"
    }
    println(runResult)
}

let和run是類似的,都會(huì)返回Lambda的執(zhí)行結(jié)果,區(qū)別在于let有Lambda參數(shù),而run沒有。但run可以使用this來訪問user對(duì)象里面的公開屬性和函數(shù)。

also和apply也是類似的

user.also { param->
    println("also ${param.name}")
}.apply { //this:User
    println("apply ${this.name}")
}

also和apply返回的是當(dāng)前執(zhí)行的對(duì)象,also有Lambda參數(shù)(這里的Lambda參數(shù)就是當(dāng)前執(zhí)行的對(duì)象),而apply沒有Lambda參數(shù)(而是通過this來訪問當(dāng)前執(zhí)行的對(duì)象)。

repeat是重復(fù)執(zhí)行當(dāng)前Lambda

repeat(5) {
    println(user.name)
}

with比較特殊,它不是以擴(kuò)展方法的形式存在的,而是一個(gè)頂級(jí)函數(shù)

with(user) { //this: User
    println("with ${this.name}")
}

with的Lambda內(nèi)部沒有參數(shù),而是可以通過this來訪問傳入對(duì)象的公開屬性和函數(shù)。

3.源碼賞析

使用這塊的話,不多說,想必大家已經(jīng)非常熟悉,我們直接開始源碼賞析。

3.1 let和run

//let和run是類似的,都會(huì)返回Lambda的執(zhí)行結(jié)果,區(qū)別在于let有Lambda參數(shù),而run沒有。但run可以使用this來訪問user對(duì)象里面的公開屬性和函數(shù)。
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
  • let和run都是擴(kuò)展函數(shù)

  • let的Lambda有參數(shù),該參數(shù)就是T,也就是待擴(kuò)展的那個(gè)對(duì)象,所以可以在Lambda內(nèi)訪問該參數(shù),從而訪問該參數(shù)對(duì)象的內(nèi)部公開屬性和函數(shù)

  • run的Lambda沒有參數(shù),但這個(gè)Lambda是待擴(kuò)展的那個(gè)對(duì)象T的擴(kuò)展,這是帶接收者的函數(shù)類型,所以可以看做這個(gè)Lambda是T的成員函數(shù),直接調(diào)用該Lambda就是相當(dāng)于直接調(diào)用該T對(duì)象的成員函數(shù),所以在該Lambda內(nèi)部可以通過this來訪問T的公開屬性和函數(shù)(只能訪問公開的,稍后解釋是為什么)。

  • let和run都是返回的Lambda的執(zhí)行結(jié)果

3.2 also和apply

//also和apply都是返回原對(duì)象本身,區(qū)別是apply沒有Lambda參數(shù),而also有
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
  • also和apply都是擴(kuò)展函數(shù)

  • also和apply都是返回原對(duì)象本身,區(qū)別是apply沒有Lambda參數(shù),而also有

  • also的Lambda有參數(shù),該參數(shù)就是T,也就是待擴(kuò)展的那個(gè)對(duì)象,所以可以在Lambda內(nèi)訪問該參數(shù),從而訪問該參數(shù)對(duì)象的內(nèi)部公開屬性和函數(shù)

  • apply的Lambda沒有參數(shù),但這個(gè)Lambda是待擴(kuò)展的那個(gè)對(duì)象T的擴(kuò)展,這是帶接收者的函數(shù)類型,所以可以看做這個(gè)Lambda是T的成員函數(shù),直接調(diào)用該Lambda就是相當(dāng)于直接調(diào)用該T對(duì)象的成員函數(shù),所以在該Lambda內(nèi)部可以通過this來訪問T的公開屬性和函數(shù)(只能訪問公開的,稍后解釋是為什么)。

3.3 repeat

public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }
    for (index in 0 until times) {
        action(index)
    }
}
  • repeat是一個(gè)頂層函數(shù)

  • 該函數(shù)有2個(gè)參數(shù),一個(gè)是重復(fù)次數(shù),另一個(gè)是需執(zhí)行的Lambda,Lambda帶參數(shù),該參數(shù)表示第幾次執(zhí)行

  • 函數(shù)內(nèi)部非常簡單,就是一個(gè)for循環(huán),執(zhí)行Lambda

3.4 with

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
  • with是一個(gè)頂層函數(shù)

  • with有2個(gè)參數(shù),一個(gè)是接收者,一個(gè)是帶接收者的函數(shù)

  • with的返回值就是block函數(shù)的返回值

  • block是T的擴(kuò)展,所以可以使用receiver對(duì)象直接調(diào)用block函數(shù),而且block內(nèi)部可以使用this來訪問T的公開屬性和函數(shù)

4.反編譯

了解一下這些作用域函數(shù)編譯之后到底長什么樣子,先看下demo

data class User(val name: String)
fun main() {
    val user = User("云天明")
    val letResult = user.let { param ->
        "let 輸出點(diǎn)東西 ${param.name}"
    }
    println(letResult)
    val runResult = user.run {  //this:User
        "run 輸出點(diǎn)東西 ${this.name}"
    }
    println(runResult)
    user.also { param ->
        println("also ${param.name}")
    }.apply { //this:User
        println("apply ${this.name}")
    }
    repeat(5) {
        println(user.name)
    }
    val withResult = with(user) { //this: User
        println("with ${this.name}")
        "with 輸出點(diǎn)東西 ${this.name}"
    }
    println(withResult)
}

然后反編譯看一下,data class的反編譯咱就不看了,只關(guān)注main內(nèi)部的代碼

User user = new User("云天明");
System.out.println("let 輸出點(diǎn)東西 " + user.getName());
System.out.println("run 輸出點(diǎn)東西 " + user.getName());
User $this$test_u24lambda_u2d3 = user;
System.out.println("also " + $this$test_u24lambda_u2d3.getName());
System.out.println("apply " + $this$test_u24lambda_u2d3.getName());
for (int i = 0; i < 5; i++) {
    int i2 = i;
    System.out.println(user.getName());
}
User $this$test_u24lambda_u2d5 = user;
System.out.println("with " + $this$test_u24lambda_u2d5.getName());
System.out.println("with 輸出點(diǎn)東西 " + $this$test_u24lambda_u2d5.getName());

可以看到,let、run、also、apply、repeat、with的Lambda內(nèi)部執(zhí)行的東西,全部放外面來了(因?yàn)閕nline),不用把Lambda轉(zhuǎn)換成Function(匿名內(nèi)部類啥的),這樣執(zhí)行起來性能會(huì)高很多。

額&hellip;我其實(shí)還想看一下block: T.() -> R這種編譯出來是什么樣子的,上面的那些作用域函數(shù)全部是inline的函數(shù),看不出來了。我自己寫一個(gè)看一下,自己寫幾個(gè)類似let、run、with的函數(shù),但不帶inline:

public fun <T, R> T.letMy(block: (T) -> R): R {
    return block(this)
}
public fun <T, R> T.runMy(block: T.() -> R): R {
    return block()
}
public fun <T, R> withMy(receiver: T, block: T.() -> R): R {
    return receiver.block()
}
fun test() {
    val user = User("云天明")
    val letResult = user.letMy { param ->
        "let 輸出點(diǎn)東西 ${param.name}"
    }
    println(letResult)
    val runResult = user.runMy {  //this:User
        "run 輸出點(diǎn)東西 ${this.name}"
    }
    println(runResult)
    val withResult = withMy(user) { //this: User
        println("with ${this.name}")
        "with 輸出點(diǎn)東西 ${this.name}"
    }
    println(withResult)
}

反編譯出來的樣子:

final class TestKt$test$letResult$1 extends Lambda implements Function1<User, String> {
    public static final TestKt$test$letResult$1 INSTANCE = new TestKt$test$letResult$1();
    TestKt$test$letResult$1() {
        super(1);
    }
    public final String invoke(User param) {
        Intrinsics.checkNotNullParameter(param, "param");
        return "let 輸出點(diǎn)東西 " + param.getName();
    }
}
final class TestKt$test$runResult$1 extends Lambda implements Function1<User, String> {
    public static final TestKt$test$runResult$1 INSTANCE = new TestKt$test$runResult$1();
    TestKt$test$runResult$1() {
        super(1);
    }
    public final String invoke(User $this$runMy) {
        Intrinsics.checkNotNullParameter($this$runMy, "$this$runMy");
        return "run 輸出點(diǎn)東西 " + $this$runMy.getName();
    }
}
final class TestKt$test$withResult$1 extends Lambda implements Function1<User, String> {
    public static final TestKt$test$withResult$1 INSTANCE = new TestKt$test$withResult$1();
    TestKt$test$withResult$1() {
        super(1);
    }
    public final String invoke(User $this$withMy) {
        Intrinsics.checkNotNullParameter($this$withMy, "$this$withMy");
        System.out.println("with " + $this$withMy.getName());
        return "with 輸出點(diǎn)東西 " + $this$withMy.getName();
    }
}
public final class TestKt {
    public static final <T, R> R letMy(T $this$letMy, Function1<? super T, ? extends R> block) {
        Intrinsics.checkNotNullParameter(block, "block");
        return block.invoke($this$letMy);
    }
    public static final <T, R> R runMy(T $this$runMy, Function1<? super T, ? extends R> block) {
        Intrinsics.checkNotNullParameter(block, "block");
        return block.invoke($this$runMy);
    }
    public static final <T, R> R withMy(T receiver, Function1<? super T, ? extends R> block) {
        Intrinsics.checkNotNullParameter(block, "block");
        return block.invoke(receiver);
    }
    public static final void test() {
        User user = new User("云天明");
        System.out.println((String) letMy(user, TestKt$test$letResult$1.INSTANCE));
        System.out.println((String) runMy(user, TestKt$test$runResult$1.INSTANCE));
        System.out.println((String) withMy(user, TestKt$test$withResult$1.INSTANCE));
    }
}

在我寫的demo中l(wèi)etMy、runMy、withMy的Lambda都被編譯成了匿名內(nèi)部類,它們都繼承自kotlin.jvm.internal.Lambda這個(gè)類,且都實(shí)現(xiàn)了Function1<User, String>接口。

abstract class Lambda<out R>(override val arity: Int) : FunctionBase<R>, Serializable {
    override fun toString(): String = Reflection.renderLambdaToString(this)
}
interface FunctionBase<out R> : Function<R> {
    val arity: Int
}
public interface Function<out R>
public interface Function1<in P1, out R> : Function<R> {
    public operator fun invoke(p1: P1): R
}

這里的Lambda是一個(gè)Kotlin內(nèi)置的一個(gè)類,它就是一個(gè)Function,用來表示函數(shù)類型的值。而Function1則是繼承自Function,它表示有一個(gè)參數(shù)的函數(shù)類型。除了Function1,Kotlin還內(nèi)置了Function2、Function3、Function4等等,分別代表了2、3、4個(gè)參數(shù)的函數(shù)類型。就是這么簡單粗暴。

回到上面的反編譯代碼中,我們發(fā)現(xiàn)letMy函數(shù),傳入user對(duì)象和TestKt$test$letResult$1.INSTANCE這個(gè)單例對(duì)象,并且在執(zhí)行的時(shí)候,是用單例對(duì)象調(diào)用invoke函數(shù),然后將user傳進(jìn)去的。在TestKt$test$letResult$1#invoke中,接收到了user對(duì)象,然后通過該對(duì)象訪問其函數(shù)。可以看到,這里是用user對(duì)象去訪問對(duì)象中的屬性或者函數(shù),那么肯定是只能訪問到公開的屬性和函數(shù),這也就解答了上面的疑惑。

其他2個(gè),runMy和withMy函數(shù),竟然在編譯之后和letMy長得一模一樣。這意味著block: (T) -> Rblock: T.() -> R是類似的,編譯之后代碼一模一樣。都是將T對(duì)象傳入invoke函數(shù),然后在invoke函數(shù)內(nèi)部進(jìn)行操作T對(duì)象。

關(guān)于“Kotlin作用域函數(shù)怎么應(yīng)用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Kotlin作用域函數(shù)怎么應(yīng)用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI