• Kotlin~作用域函数let、run、apply、with


    封面


    前言

    本文比较总结Kotlin标准库中提供的作用域函数。kt中它们的使用有的时候,我们去看别人的代码感觉一团糟,所以想理清一下。就比如我们去吃西餐,我们是用刀子和叉子,想必作为一名开发者一定要用对!


    一、高阶函数概念

    函数中有lambda,函数的函数就是高阶函数。编码习惯上,java的函数是先有输出再有输入,kt的函数先有输入再有输出。
    那么在kt中什么是作用域函数 = 高阶函数+扩展函数,我们也可以自己的作用域函数。

    /**
     * 高阶函数+扩展函数
     * fun <万能类型> 万能类型.run( block:万能类型.() -> (万能类型)) = block()
     * 第一个this是被拓展出来的,调用端
     * 第二个this是匿名扩展,lambda里面
     * T.()会给block的lambda会让lambda实现体里面持有this == T本身。
     * T.(T)会给block的lambda会让lambda实现体里面持有it== T本身。
     */
    inline fun <T, R> T.run(block: (T) -> (R)): R {
        return block(this)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    二、函数

    1.let

    public inline fun <T, R> T.let(block: (T) -> R): R
    
    • 1

    定义:
    类型T的扩展函数,在let块内可以通过it指代该对象。返回值为let块的最后一行或return表达式。
    用途:

    • 对调用链结果进行操作
    fun main() {
        val numbers = mutableListOf("One", "Two", "Three", "Four", "Five")
        numbers.map {
            it.length
        }.filter {
            it > 3
        }.let {
            print(it)
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 将it重命名一个可读的lambda参数
    fun main() {
        val book = Book().let { book ->
            book.name = "《计算机网络》"
        }
        print(book)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.run

    public inline fun <T, R> T.run(block: T.() -> R): R
    
    • 1

    定义:
    和let类似,通过this指代该对象,只不过内部可以很方便使用该对象的属性和方法。
    另外一种声明不是扩展函数,不是用来传递对象修改属性,通常用来执行代码块或具名函数。
    用途:
    适用于let、with的任何场景,是let和with结合体。

    public inline fun <R> run(block: () -> R): R 
    
    • 1

    3.with

    public inline fun <T, R> with(receiver: T, block: T.() -> R): R 
    
    • 1

    定义:
    属于非扩展函数,直接输入一个对象,修改其属性,可以和run做同样的事。区别是当函数块不需要返回值时,可以使用它。

    用途:

    • 适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中
      onBinderViewHolder中,数据model的属性映射到UI上

    wifi和run反编译出来的结果很像,但原理不一样。

        fun convert1(holder: BaseViewHolder, item: Student) {
            with(item) {
                holder.setText(1, id)
                holder.setText(2, name)
                holder.setVisible(3, sex)
                holder.setText(2, intro)
            }
        }
    
        fun convert2(holder: BaseViewHolder, item: Student) {
            item.run {
                holder.setText(1, id)
                holder.setText(2, name)
                holder.setVisible(3, sex)
                holder.setText(2, intro)
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    @Metadata(mv={1, 5, 1}, k=1, xi=48, d1={"..."})
    public final class KtStd {
        public final void convert1(@NotNull BaseViewHolder holder, @NotNull Student item) {
            Intrinsics.checkNotNullParameter((Object)((Object)holder), (String)"holder");
            Intrinsics.checkNotNullParameter((Object)item, (String)"item");
            boolean bl = false;
            boolean bl2 = false;
            Student $this$convert1_u24lambda_u2d0 = item;
            boolean bl3 = false;
            holder.setText(1, $this$convert1_u24lambda_u2d0.getId());
            holder.setText(2, $this$convert1_u24lambda_u2d0.getName());
            holder.setVisible(3, $this$convert1_u24lambda_u2d0.getSex());
            holder.setText(2, $this$convert1_u24lambda_u2d0.getIntro());
        }
    
        public final void convert2(@NotNull BaseViewHolder holder, @NotNull Student item) {
            Intrinsics.checkNotNullParameter((Object)((Object)holder), (String)"holder");
            Intrinsics.checkNotNullParameter((Object)item, (String)"item");
            Student student = item;
            boolean bl = false;
            boolean bl2 = false;
            Student $this$convert2_u24lambda_u2d1 = student;
            boolean bl3 = false;
            holder.setText(1, $this$convert2_u24lambda_u2d1.getId());
            holder.setText(2, $this$convert2_u24lambda_u2d1.getName());
            holder.setVisible(3, $this$convert2_u24lambda_u2d1.getSex());
            holder.setText(2, $this$convert2_u24lambda_u2d1.getIntro());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    4.apply

    @kotlin.internal.InlineOnly
    public inline fun <T> T.apply(block: T.() -> Unit): T
    
    • 1
    • 2

    T的扩展函数,和run类似,对象的上下文引用为this,apply不接收函数块中的返回值,返回自己的T类型对象。
    用途:

    • 对象初始化或更改对象属性

    5.also

    public inline fun <T> T.also(block: (T) -> Unit): T
    
    • 1

    使用场景:
    返回值和apply一样,直接返回T。用法类似于let使用it作为上下文。
    那么使用区别就是,适用let但需要返回自身的场景。

    6.takeIf & takeUnless

    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
        contract {
            callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
        }
        return if (predicate(this)) this else null
    }
    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
        contract {
            callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
        }
        return if (!predicate(this)) this else null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果、除非函数是一对过滤操作函数,应用在链式调用过程中,对对象的状态进行检查,根据条件决定返回空还是自身。

    8.repeat

    @kotlin.internal.InlineOnly
    public inline fun repeat(times: Int, action: (Int) -> Unit) {
        contract { callsInPlace(action) }
    
        for (index in 0 until times) {
            action(index)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    本质就是一个工具函数,不用多讲。有些时候,我们可以用它来替代一下for循环。

    三、如何选择?

    内置函数都提供空安全检查,结合使用场景和实现差异,我们可以得出下面选用逻辑。(来自互联网)
    函数选择

    思考总结


    这里就引用前辈们的总结:

    我们可以看出在这五个通用标准函数当中它们的特性也是十分的简单,无非也就是接收者和返回值的不同。对于with,T.run,T.apply接收者是this,而T.let和T.also接受者是it;对于with,T.run,T.let返回值是作用域的最后一个对象(this),而T.apply和T.also返回值是调用者本身(itself)。

    我觉得还需要注意以下几点,做一下记录

    1. 代码上尽量减少扩展函数的嵌套,否则你会发现自己给自己绕晕了。虽然可以取lambda别名,最好不要超过3层,嵌套过多代码可读性比较差。
    2. 通过扩展函数源码,我们发现kt很好的把函数+表达式融入到了一起,很方便我们业务开发使用,这也许就是现代编程语言的特性。

    参考:

  • 相关阅读:
    electron 应用开发优秀实践
    [附源码]Java计算机毕业设计SSM高校本科毕业及资料存档管理系统
    Ceph Iscsi GW
    👍SpringSecurity单体项目最佳实践
    全流程机器视觉工程开发(二)PaddleDetection:拉框,然后开始训练模型
    创投课程研报专题课 | 如何写出高质量研报
    Chrome自动升级了,找不到最新版本的webdriver怎么办?
    HTML5+CSS3小实例:悬停放大图片的旅游画廊
    电子标准院工程师鲍薇:人工智能标准化引领产业发展
    Kubernetes 系统监控Metrics Server、HorizontalPodAutoscaler、Prometheus
  • 原文地址:https://blog.csdn.net/Bluechalk/article/details/125309003