返回顶部
首页 > 资讯 > 移动开发 >Kotlin作用域函数应用详细介绍
  • 899
分享到

Kotlin作用域函数应用详细介绍

2024-04-02 19:04:59 899人浏览 独家记忆
摘要

目录1.前置知识2.使用3.源码赏析3.1 let和run3.2 also和apply3.3 repeat3.4 with4.反编译5.小结平时看博客或者学知识,学到的东西比较零散,

平时看博客或者学知识,学到的东西比较零散,没有独立的知识模块概念,而且学了之后很容易忘。于是我建立了一个自己的笔记仓库 (一个我长期维护的笔记仓库,感兴趣的可以点个star~你的star是我写作的巨大大大大的动力),将平时学到的东西都归类然后放里面,需要的时候呢也方便复习。

1.前置知识

Kotlin中,函数是一等公民,它也是有自己的类型的。比如()->Unit,函数类型是可以被存储在变量中的。

Kotlin中的函数类型形如:()->Unit(Int,Int)->StringInt.(String)->String等。它们有参数和返回值。

最后一个Int.(String)->String比较奇怪,它表示函数类型可以有一个额外的接收者类型。这里表示可以在Int对象上调用一个String类型参数并返回一个String类型的函数。

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

如果我们把Int.(String) -> String类型定义成变量,并给它赋值,后面的Lambda的参数param就是传入的String类型,最后返回值也是String,而在这个Lambda中用this表示前面的接收者类型Int的对象,有点像扩展函数,可以在函数内部通过this来访问一些成员变量、成员方法什么的。可以把这种带接收者的函数类型,看成是成员方法。

因为它的声明方式有点像扩展函数,所以我们可以使用1.test("2")来调用test这个函数类型,它其实编译之后最终是将1这个Int作为参数传进去的。所以后面的test(1, "2")这种调用方式也是OK的。

有了上面的知识补充,咱们再来看Kotlin的标准库函数apply

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
  • 首先apply是一个扩展函数,其次是带泛型的,意味着任何对象都可以调用apply函数。
  • 接着它的参数是带接收者的函数类型,接收者是T,那么调用block()就像是调用了T对象里面的一个成员函数一样,在block函数内部可以使用this来对公开的成员变量和公开的成员函数进行访问
  • 返回值:就是T,哪个对象调用的该扩展函数就返回哪个对象

2.使用

作用域函数是Kotlin内置的,可对数据进行操作转换等。

先来看个demo,let和run

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

    val user = User("云天明")
    val letResult = user.let { param ->
        "let 输出点东西 ${param.name}"
    }
    println(letResult)
    val runResult = user.run {  //this:User
        "run 输出点东西 ${this.name}"
    }
    println(runResult)
}

let和run是类似的,都会返回Lambda的执行结果,区别在于let有Lambda参数,而run没有。但run可以使用this来访问user对象里面的公开属性和函数。

also和apply也是类似的

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

also和apply返回的是当前执行的对象,also有Lambda参数(这里的Lambda参数就是当前执行的对象),而apply没有Lambda参数(而是通过this来访问当前执行的对象)。

repeat是重复执行当前Lambda

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

with比较特殊,它不是以扩展方法的形式存在的,而是一个顶级函数

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

with的Lambda内部没有参数,而是可以通过this来访问传入对象的公开属性和函数。

3.源码赏析

使用这块的话,不多说,想必大家已经非常熟悉,我们直接开始源码赏析。

3.1 let和run

//let和run是类似的,都会返回Lambda的执行结果,区别在于let有Lambda参数,而run没有。但run可以使用this来访问user对象里面的公开属性和函数。
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都是扩展函数
  • let的Lambda有参数,该参数就是T,也就是待扩展的那个对象,所以可以在Lambda内访问该参数,从而访问该参数对象的内部公开属性和函数
  • run的Lambda没有参数,但这个Lambda是待扩展的那个对象T的扩展,这是带接收者的函数类型,所以可以看做这个Lambda是T的成员函数,直接调用该Lambda就是相当于直接调用该T对象的成员函数,所以在该Lambda内部可以通过this来访问T的公开属性和函数(只能访问公开的,稍后解释是为什么)。
  • let和run都是返回的Lambda的执行结果

3.2 also和apply

//also和apply都是返回原对象本身,区别是apply没有Lambda参数,而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都是扩展函数
  • also和apply都是返回原对象本身,区别是apply没有Lambda参数,而also有
  • also的Lambda有参数,该参数就是T,也就是待扩展的那个对象,所以可以在Lambda内访问该参数,从而访问该参数对象的内部公开属性和函数
  • apply的Lambda没有参数,但这个Lambda是待扩展的那个对象T的扩展,这是带接收者的函数类型,所以可以看做这个Lambda是T的成员函数,直接调用该Lambda就是相当于直接调用该T对象的成员函数,所以在该Lambda内部可以通过this来访问T的公开属性和函数(只能访问公开的,稍后解释是为什么)。

3.3 repeat

public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }
    for (index in 0 until times) {
        action(index)
    }
}
  • repeat是一个顶层函数
  • 该函数有2个参数,一个是重复次数,另一个是需执行的Lambda,Lambda带参数,该参数表示第几次执行
  • 函数内部非常简单,就是一个for循环,执行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是一个顶层函数
  • with有2个参数,一个是接收者,一个是带接收者的函数
  • with的返回值就是block函数的返回值
  • block是T的扩展,所以可以使用receiver对象直接调用block函数,而且block内部可以使用this来访问T的公开属性和函数

4.反编译

了解一下这些作用域函数编译之后到底长什么样子,先看下demo

data class User(val name: String)
fun main() {
    val user = User("云天明")
    val letResult = user.let { param ->
        "let 输出点东西 ${param.name}"
    }
    println(letResult)
    val runResult = user.run {  //this:User
        "run 输出点东西 ${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 输出点东西 ${this.name}"
    }
    println(withResult)
}

然后反编译看一下,data class的反编译咱就不看了,只关注main内部的代码

User user = new User("云天明");
System.out.println("let 输出点东西 " + user.getName());
System.out.println("run 输出点东西 " + 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 输出点东西 " + $this$test_u24lambda_u2d5.getName());

可以看到,let、run、also、apply、repeat、with的Lambda内部执行的东西,全部放外面来了(因为inline),不用把Lambda转换成Function(匿名内部类啥的),这样执行起来性能会高很多。

额…我其实还想看一下block: T.() -> R这种编译出来是什么样子的,上面的那些作用域函数全部是inline的函数,看不出来了。我自己写一个看一下,自己写几个类似let、run、with的函数,但不带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 输出点东西 ${param.name}"
    }
    println(letResult)
    val runResult = user.runMy {  //this:User
        "run 输出点东西 ${this.name}"
    }
    println(runResult)
    val withResult = withMy(user) { //this: User
        println("with ${this.name}")
        "with 输出点东西 ${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 输出点东西 " + 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 输出点东西 " + $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 输出点东西 " + $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中letMy、runMy、withMy的Lambda都被编译成了匿名内部类,它们都继承自kotlin.JVM.internal.Lambda这个类,且都实现了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是一个Kotlin内置的一个类,它就是一个Function,用来表示函数类型的值。而Function1则是继承自Function,它表示有一个参数的函数类型。除了Function1,Kotlin还内置了Function2、Function3、Function4等等,分别代表了2、3、4个参数的函数类型。就是这么简单粗暴。

回到上面的反编译代码中,我们发现letMy函数,传入user对象和TestKt$test$letResult$1.INSTANCE这个单例对象,并且在执行的时候,是用单例对象调用invoke函数,然后将user传进去的。在TestKt$test$letResult$1#invoke中,接收到了user对象,然后通过该对象访问其函数。可以看到,这里是用user对象去访问对象中的属性或者函数,那么肯定是只能访问到公开的属性和函数,这也就解答了上面的疑惑。

其他2个,runMy和withMy函数,竟然在编译之后和letMy长得一模一样。这意味着block: (T) -> Rblock: T.() -> R是类似的,编译之后代码一模一样。都是将T对象传入invoke函数,然后在invoke函数内部进行操作T对象。

5.小结

Kotlin作用域函数在日常编码中,使用频率极高,所以我们需要简单了解其基本原理,万一出了什么事方便找问题。理解作用域函数,得先理解函数类型,在Kotlin中函数也是有类型的,形如:()->Unit(Int,Int)->StringInt.(String)->String等,它们可以被存储与变量中。let、run、apply、also都是扩展函数,with、repeat是顶层函数,它们都是inline修饰的函数,编译之后Lambda就没了,直接把Lambda内部的代码搬到了外边,提高了性能。

感谢大家的观看,希望本文能帮助大家更深地理解作用域函数。

课后小练习:

如果你觉得自己完全理解了本文,不妨拿出文本编辑器,把let、run、apply、also、with、repeat默写出来,可能会有更深地理解效果。

到此这篇关于Kotlin作用域函数应用详细介绍的文章就介绍到这了,更多相关Kotlin作用域内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Kotlin作用域函数应用详细介绍

本文链接: https://lsjlt.com/news/165904.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Kotlin作用域函数应用详细介绍
    目录1.前置知识2.使用3.源码赏析3.1 let和run3.2 also和apply3.3 repeat3.4 with4.反编译5.小结平时看博客或者学知识,学到的东西比较零散,...
    99+
    2024-04-02
  • Kotlin作用域函数使用示例详细介绍
    目录1 let2 run3 with4 apply5 also这里我们将介绍Kotlin 5个作用域函数:let,run,with,apply,also。 1 let let 可用于...
    99+
    2023-02-17
    Kotlin作用域函数 Kotlin作用域
  • Kotlin扩展函数超详细介绍
    目录1.扩展函数2.infix 关键字3.扩展函数文件4.重命名扩展函数1.扩展函数 1)当我们没法接触某个类的定义,或者某个类没有用open修饰无法继承时,我们可以通过扩展函数,来...
    99+
    2024-04-02
  • Kotlin挂起函数的详细介绍
    Kotlin 协程的优势: 解决回调地狱的问题。以同步的方式完成异步任务。 示例: fun main() { runBlocking { val a = ge...
    99+
    2024-04-02
  • Kotlin函数式编程超详细介绍
    目录1.函数式编程2.函数类别3.变换函数(transform)4.过滤函数(filter)5.合并函数(combine)1.函数式编程 我们都知道java是面向对象编程范式。在ja...
    99+
    2024-04-02
  • Kotlin匿名函数使用介绍
    目录1.函数的声明2.函数参数3.Unit函数4.匿名函数5.匿名函数的参数6.lambda表达式7.定义参数8.匿名函数9.函数的引用10.函数类型作为返回类型1.函数的声明 ko...
    99+
    2024-04-02
  • JavaScript函数的详细介绍
    本篇内容主要讲解“JavaScript函数的详细介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript函数的详细介绍”吧!一、函数语法一个Jav...
    99+
    2024-04-02
  • SpringCloudFeign配置应用详细介绍
    目录前言1、Feign简介2、Feign配置应用前言 服务消费者调用服务提供者的时候使用RestTemplate技术 存在不便之处: 拼接urlrestTmplate.getFor...
    99+
    2024-04-02
  • Python 循环函数详细介绍
    目录一、循环函数1、for循环2、while循环3、中断循环二、循环设计1、range()2、enumerate()3、zip()三、循环对象1、什么是循环对象2、迭代器3、生成器4...
    99+
    2024-04-02
  • KotlinExtensionFunction扩展函数详细介绍
    目录1 什么是扩展函数(Extension Function)2 为什么使用扩展函数(Extension Function)3 如何使用扩展函数(Extension Function...
    99+
    2023-02-17
    Kotlin Extension Function Kotlin扩展函数
  • JavaScript的function函数详细介绍
    通过函数来封装任意多条语句,而且可以在任何地方、任何时间调用执行。 而我们的JavaScript脚本语言比较特殊,相对于C语言,它的参数是不需要数据类型加持的。返回值return,...
    99+
    2024-04-02
  • Java 函数编程详细介绍
    目录一、函数编程Lambda1、接口二、Java函数接口1、Predicate2、Consumer3、Function4、Supplier三、类型检查1、Capturing Lamb...
    99+
    2024-04-02
  • Shell中的函数、函数定义、作用域问题介绍
    说起函数调用,相信大家也不会陌生,然而对于初学Shell的我来说,Shell中函数调用方式却有点让我不太习惯,自己也走了不少的弯路,因为传递参数时出了一个很“自然”的错误,也让我吃了不少的苦头,所以总结一下...
    99+
    2022-06-04
    函数 定义 作用
  • SpringMVC数据响应详细介绍
    目录一、环境准备二、响应页面[了解]三、返回文本数据[了解]四、响应JSON数据SpringMVC接收到请求和数据后,进行一些了的处理,当然这个处理可以是转发给Service,Ser...
    99+
    2023-02-06
    SpringMVC数据响应 SpringMVC响应
  • Android应用 坐标系详细介绍
    Android 应用坐标系详解:             ...
    99+
    2022-06-06
    Android
  • Python嵌套函数与nonlocal使用详细介绍
    目录嵌套函数嵌套函数中变量的范围nonlocal使用nonlocal的优点缺点举例多层嵌套中的nonlocal嵌套函数中局部变量的重用理解闭包之前,我们首先需要理解什么是嵌套函数(n...
    99+
    2024-04-02
  • Golang内置函数使用方法详细介绍
    Golang是一种非常强大的编程语言,其提供许多内置函数以方便开发者进行编程。在本文中,我们将详细介绍Golang内置函数的使用方法,以供开发者参考。make函数make函数主要用于创建一个数据类型的对象(slice、map或channel...
    99+
    2023-05-16
    Golang(Go语言) 内置函数(built-in functions) 使用方法(usage methods)
  • Kotlin标准库函数使用分析及介绍
    目录1.apply 函数2.let 函数3.run函数4.with 函数5.also6.takeIf7.takeUnless1.apply 函数 apply函数可以看做是一个配置函数...
    99+
    2024-04-02
  • Python max内置函数详细介绍
    Python max内置函数 max(iterable, *[, key, default]) max(arg1, arg2, *args[, key]) Return the largest item i...
    99+
    2022-06-04
    详细介绍 函数 Python
  • 详细介绍Java函数式接口
    目录Java—函数式接口1.自定义函数式接口1.1概述1.2格式1.3@FunctionalInterface注解1.4自定义函数式接口2.函数式编程2.1Lambda的延迟执行2....
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作