Kotlin高阶扩展函数:理解let、with、run、apply、also函数

    科技2026-02-01  5

    前言

    为了方便开发者更加友好的代码编写,kotlin提供了高阶扩展函数let,with,run,apply。在理解之前,需要我们理解 扩展函数 与 高阶函数 的概念。

    扩展函数

    Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展的特殊声明完成。

    如我们想为String类型扩展一个打印方法,直接定义一个扩展函数即可,无需通过集成的方式去扩展。

    fun String.println() { println(this.toString()); } "hahah".println();

    高阶函数

    Kotlin支持函数的参数类型或返回值是函数,对这样的函数我们称之为高阶函数

    如Kotlin中的let高阶函数

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

    其中block参数即为函数类型的参数 (T)-> R 表示函数的入参为T类型返回类型为R类型

    函数类型的表示方式:

    1.所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型: (A, B) -> C表示接受类型分别为 A与 B两个参数并返回一个 C类型值的函数类型 2.函数类型可以有一个额外的 接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C表示可以在 A的接收者对象上以一个 B类型参数来调用并返回一个 C类型值的函数。

    函数类型实例化方式:这里我们只详细说明一下lambda表达式

    { x: Int, y: Int -> x + y } lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 ->符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个) 表达式会视为返回值

    let函数定义

    在熟悉上面两个概念我们来看一下let函数的定义

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

    T.let表示为任何类型扩展了let方法,所以任何对象都可以调用let方法,参数为一个函数类型是(T)-> R 。return block(this)可以看出函数的入参为对象本身,返回值为函数的最后一行代码。

    let函数使用场景

    由于函数的入参为一个参数,所以我们可以省略,用it代替。

    mTextView?.let { it.setText("haha") it.setTextColor(Color.BLACK) }

    also函数定义

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

    also函数与let函数使用上差异不大,只是函数返回类型存在差异,let返回lamdba表达式最后一行代码,also返回调用者对象本身

    also函数使用场景

    val tv:TextView?= mTextView?.also { it.setText("haha") it.setTextColor(Color.BLACK) }

     

    apply函数定义

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

    同样apply参数的函数类型为T.()-> Unit 可以理解为为T类型扩展一个入参为空,返回只为空的函数。这样传入的函数就可以直接访问T类型对象内部的方法或者变量,而不用添加it

    apply函数使用场景

    val tv: TextView? = mTextView?.apply { text ="haha" setTextColor(Color.BLACK) }

    可以看出apply扩展函数与let扩展函数对比,省略了it,另外返回值存在差异let返回lamdba表达式最后一行代码,apply返回调用者对象本身

    with函数定义

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

    可以看出with是一个正常函数。

    with函数使用场景

    with(mTextView){ this?.text ?:= "haha" this?.setTextColor(Color.BLACK) }

    因为可能为空所以需要添加判空处理

    run函数定义

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

    run函数与apply函数差异不大。只是返回类型存在差异,apply函数返回调用者对象本身,run函数则返回amdba表达式最后一行代码

    run函数使用场景

    mTextView?.run { text ="haha" setTextColor(Color.BLACK) }
    Processed: 0.032, SQL: 9