当前位置:首页 > 文章列表 > 文章 > 软件教程 > Kotlin run 详解:把对象操作收进作用域,再把结果带出来

Kotlin run 详解:把对象操作收进作用域,再把结果带出来

2026-08-20 22:59:31 0浏览 收藏

简介

run 是 Kotlin 标准库里的作用域函数。

Kotlin run 详解:把对象操作收进作用域,再把结果带出来

作用域函数常见有 5 个:

  • let
  • run
  • with
  • apply
  • also

run 的特点比较鲜明:

在对象作用域里执行一段逻辑,然后返回 Lambda 最后一行的结果。

常见写法:

val result = user.run {
    "$name-$age"
}

在这儿,nameage均源自user对象,而result则是Lambda最后一行的字符串哦。

run 适合这类场景:

  • 在一个对象里连续读取多个属性
  • 对对象做一段计算并返回结果
  • 可空对象非空时执行并返回结果
  • 创建一个临时作用域,收拢中间变量
  • 配合 apply 完成“先配置,再计算”

run 有两种形式

Kotlin 里有两种 run

对象扩展函数 run

val result = obj.run {
    // this 是 obj
    // 最后一行作为返回值
}

源码大致可以理解成:

public inline fun  T.run(block: T.() -> R): R {
    return block()
}

特点:

Lambda 里用 this 访问对象
run 返回 Lambda 最后一行结果

顶层函数 run

val result = run {
    // 没有接收者对象
    // 最后一行作为返回值
}

源码大致可以理解成:

public inline fun  run(block: () -> R): R {
    return block()
}

特点:

没有上下文对象
主要用于创建临时作用域
返回 Lambda 最后一行结果

基础用法

先定义一个简单的数据类:

data class User(
    val name: String,
    val age: Int
)

使用 run

fun main() {
    val user = User("Tom", 18)

    val info = user.run {
        "$name-$age"
    }

    println(info)
}

输出:

Tom-18

run 代码块里,this 就是 user

下面两种写法等价:

val info = user.run {
    "$name-$age"
}
val info = user.run {
    "${this.name}-${this.age}"
}

this 通常可以省略。

run 的返回值

run 返回 Lambda 最后一行结果。

fun main() {
    val user = User("Tom", 18)

    val nextAge = user.run {
        age + 1
    }

    println(nextAge)
}

输出:

19

再看一个多行示例:

fun main() {
    val user = User("Tom", 18)

    val result = user.run {
        val adult = age >= 18
        "name=$name, adult=$adult"
    }

    println(result)
}

输出:

name=Tom, adult=true

最后一行:

"name=$name, adult=$adult"

就是整个 run 的返回值。

this 接收者

run 的扩展函数版本使用带接收者的 Lambda:

T.() -> R

所以 Lambda 里可以直接访问对象成员。

data class Product(
    val name: String,
    val price: Double,
    val discountRate: Double
)

fun main() {
    val product = Product("Keyboard", 300.0, 0.8)

    val finalPrice = product.run {
        price * discountRate
    }

    println(finalPrice)
}

输出:

240.0

如果使用 let,通常要通过 it 访问对象:

val finalPrice = product.let {
    it.price * it.discountRate
}

如果需要频繁访问同一个对象的属性,runthis 风格会更紧凑。

和 let 的区别

runlet 都返回 Lambda 结果。

区别在于上下文对象:

函数上下文对象返回值常见用途
runthisLambda 结果在对象作用域里计算结果
letitLambda 结果空安全、链式转换、显式参数

示例:

val user = User("Tom", 18)

val a = user.run {
    "$name-$age"
}

val b = user.let {
    "${it.name}-${it.age}"
}

println(a)
println(b)

输出:

Tom-18
Tom-18

对象属性较多时,run 可以少写对象名或 it

参数名能提升可读性时,let 更合适:

val result = user.let { currentUser ->
    "${currentUser.name}-${currentUser.age}"
}

和 apply 的区别

runapply 都使用 this

区别在于返回值。

函数上下文对象返回值常见用途
runthisLambda 结果计算结果
applythis对象本身初始化、配置对象

示例:

data class ServerConfig(
    var host: String = "",
    var port: Int = 0
)

fun main() {
    val config = ServerConfig().apply {
        host = "127.0.0.1"
        port = 8080
    }

    val address = config.run {
        "$host:$port"
    }

    println(address)
}

输出:

127.0.0.1:8080

apply 负责把对象配置好,run 负责从对象中计算一个结果。

可空对象配合 run

run 可以和安全调用符 ?. 配合使用。

data class Address(
    val city: String,
    val street: String
)

data class Customer(
    val name: String,
    val address: Address?
)

fun loadCustomer(id: Int): Customer? {
    return if (id == 1) {
        Customer("Tom", Address("上海", "人民路"))
    } else {
        null
    }
}

fun main() {
    val addressText = loadCustomer(1)?.run {
        val cityName = address?.city ?: "未知城市"
        "$name - $cityName"
    } ?: "客户不存在"

    println(addressText)
}

输出:

Tom - 上海

含义是:

客户非空 -> 进入 run 代码块并返回字符串
客户为空 -> 返回 客户不存在

顶层 run:临时作用域

顶层 run 没有接收者对象,主要用于创建一个临时作用域。

fun main() {
    val result = run {
        val a = 10
        val b = 20
        a + b
    }

    println(result)
}

输出:

30

ab 只在 run 代码块里有效,不会泄漏到外层作用域。

顶层 run:表达式化分支逻辑

多步计算可以用顶层 run 收成一个表达式。

fun formatInput(input: String?): String {
    return run {
        val value = input?.trim().orEmpty()

        if (value.isBlank()) {
            "EMPTY"
        } else {
            value.uppercase()
        }
    }
}

fun main() {
    println(formatInput("  kotlin  "))
    println(formatInput("   "))
}

输出:

KOTLIN
EMPTY

这里的 run 只是为了收拢临时变量 value,让函数保持表达式风格。

链式调用中使用 run

run 可以放在链式调用末尾,把前面的结果转换成最终结果。

fun main() {
    val total = listOf(1, 2, 3, 4, 5, 6)
        .filter { it % 2 == 0 }
        .map { it * it }
        .run {
            println("平方后的偶数: $this")
            sum()
        }

    println(total)
}

输出:

平方后的偶数: [4, 16, 36]
56

这里的 this 是前面链式调用得到的集合。

run 的最后一行 sum() 返回 Int,所以 totalInt

实战 Demo:订单金额计算

data class OrderItem(
    val sku: String,
    val quantity: Int,
    val price: Double
)

data class Order(
    val id: String,
    val items: List,
    val couponAmount: Double = 0.0
)

fun calculatePayAmount(order: Order): Double {
    return order.run {
        val total = items.sumOf { it.quantity * it.price }
        val afterCoupon = total - couponAmount

        if (afterCoupon 0) {
            0.0
        } else {
            afterCoupon
        }
    }
}

fun main() {
    val order = Order(
        id = "A001",
        items = listOf(
            OrderItem("BOOK", 2, 59.0),
            OrderItem("PEN", 3, 5.0)
        ),
        couponAmount = 20.0
    )

    println(calculatePayAmount(order))
}

输出:

113.0

run 把订单相关计算放在 Order 对象作用域里,最后返回应付金额。

实战 Demo:生成用户展示信息

data class UserProfile(
    val id: Long,
    val nickname: String?,
    val email: String?,
    val vipLevel: Int
)

fun buildDisplayName(profile: UserProfile): String {
    return profile.run {
        val name = nickname?.takeIf { it.isNotBlank() }
            ?: email?.substringBefore("@")
            ?: "用户$id"

        val vipText = if (vipLevel > 0) "VIP$vipLevel" else "普通用户"

        "$name ($vipText)"
    }
}

fun main() {
    val profile = UserProfile(
        id = 1001,
        nickname = null,
        email = "tom@example.com",
        vipLevel = 2
    )

    println(buildDisplayName(profile))
}

输出:

tom (VIP2)

这里需要读取 profile 的多个字段,并返回一个新字符串,适合使用 run

实战 Demo:配置后返回连接地址

data class DbConfig(
    var host: String = "",
    var port: Int = 3306,
    var database: String = "",
    var username: String = ""
)

fun main() {
    val jdbcUrl = DbConfig()
        .apply {
            host = "127.0.0.1"
            port = 3306
            database = "demo"
            username = "root"
        }
        .run {
            "jdbc:mysql://$host:$port/$database?user=$username"
        }

    println(jdbcUrl)
}

输出:

jdbc:mysql://127.0.0.1:3306/demo?user=root

apply 返回配置好的对象,run 返回基于对象生成的字符串。

实战 Demo:读取配置并转换

fun readConfig(key: String): String? {
    val configs = mapOf(
        "server.port" to "8080",
        "server.timeout" to " 30 ",
        "feature.debug" to "true"
    )

    return configs[key]
}

data class ServerOptions(
    val port: Int,
    val timeoutSeconds: Int,
    val debug: Boolean
)

fun loadOptions(): ServerOptions {
    return run {
        val port = readConfig("server.port")?.toIntOrNull() ?: 80
        val timeout = readConfig("server.timeout")?.trim()?.toIntOrNull() ?: 10
        val debug = readConfig("feature.debug")?.toBooleanStrictOrNull() ?: false

        ServerOptions(
            port = port,
            timeoutSeconds = timeout,
            debug = debug
        )
    }
}

fun main() {
    println(loadOptions())
}

输出:

ServerOptions(port=8080, timeoutSeconds=30, debug=true)

在顶层的 run 中,通过块级作用域的特性,让 porttimeoutdebug 这些临时变量局限于配置加载代码块内。这意味着它们仅在这个特定的代码块中存在并发挥作用,一旦代码执行超出这个块,这些变量就无法被访问,从而有效避免了对其他代码部分的干扰和潜在的冲突。

实战 Demo:报表统计

data class PayOrder(
    val id: String,
    val city: String,
    val amount: Double,
    val paid: Boolean
)

data class CityReport(
    val city: String,
    val orderCount: Int,
    val totalAmount: Double
)

val orders = listOf(
    PayOrder("A001", "上海", 120.0, true),
    PayOrder("A002", "北京", 80.0, false),
    PayOrder("A003", "上海", 260.0, true),
    PayOrder("A004", "深圳", 300.0, true),
    PayOrder("A005", "北京", 180.0, true)
)

需求:

  • 只统计已支付订单
  • 按城市分组
  • 生成城市报表
  • 返回报表摘要字符串

代码:

fun buildSummary(orders: ListPayOrder>): String {
    return orders
        .filter { it.paid }
        .groupBy { it.city }
        .map { (city, cityOrders) ->
            CityReport(
                city = city,
                orderCount = cityOrders.size,
                totalAmount = cityOrders.sumOf { it.amount }
            )
        }
        .sortedByDescending { it.totalAmount }
        .run {
            val totalOrders = sumOf { it.orderCount }
            val totalAmount = sumOf { it.totalAmount }
            val cityNames = joinToString { it.city }

            "城市=$cityNames, 订单数=$totalOrders, 总金额=$totalAmount"
        }
}

fun main() {
    println(buildSummary(orders))
}

输出:

城市=上海, 深圳, 北京, 订单数=4, 总金额=860.0

run 接收前面链式处理后的报表列表,并把它转换成摘要字符串。

自定义 runIfNotNull

可空对象经常使用 ?.run { },也可以封装成扩展函数。

inline fun  T?.runIfNotNull(block: T.() -> R): R? {
    return this?.run(block)
}

data class Account(
    val id: Long,
    val email: String?
)

fun main() {
    val account: Account? = Account(1, "tom@example.com")

    val domain = account.runIfNotNull {
        email?.substringAfter("@")
    }

    println(domain)
}

输出:

example.com

runIfNotNull 的返回值是 R?,对象为空时返回 null

run、let、apply、also 的区别

作用域函数主要看两个问题:

Lambda 里怎么访问对象
函数返回什么
函数上下文对象返回值常见用途
runthisLambda 结果在对象作用域里计算结果
letitLambda 结果空安全、链式转换、显式参数
applythis对象本身初始化、配置对象
alsoit对象本身日志、调试、校验、附加动作
withthisLambda 结果对已有对象集中调用

简单选择方式:

需要在对象作用域里返回一个结果:run
需要用显式参数名做转换:let
需要初始化或配置对象:apply
需要附加动作并继续返回原对象:also

run 和 with 的关系

runwith 都使用 this,都返回 Lambda 结果。

区别是调用方式:

val result1 = user.run {
    "$name-$age"
}
val result2 = with(user) {
    "$name-$age"
}

run 是扩展函数,可以配合安全调用:

val result = userOrNull?.run {
    "$name-$age"
}

with 是普通函数,不适合直接写成 userOrNull?.with { } 这种形式。

常见注意点

run 返回最后一行,不返回对象本身

fun main() {
    val result = User("Tom", 18).run {
        age + 1
    }

    println(result)
}

输出:

19

如果需要返回对象本身,使用 apply

val user = User("Tom", 18).apply {
    println(name)
}

只做日志时 also 更贴近语义

val result = listOf(1, 2, 3)
    .also { println(it) }
    .sum()

如果只是插入日志,并且要继续返回原对象,also 更合适。

顶层 run 不宜包住整段函数

fun createUser(): User {
    return run {
        val name = "Tom"
        val age = 18
        User(name, age)
    }
}

这段代码能运行。

如果整个函数只有这一段逻辑,直接写通常更简单:

fun createUser(): User {
    val name = "Tom"
    val age = 18
    return User(name, age)
}

顶层 run 更适合收拢局部临时变量,或者让某段逻辑作为表达式参与赋值。

嵌套 run 需要留意 this

outer.run {
    inner.run {
        // this 是 inner
    }
}

嵌套层级增加后,可以使用标签或者提取函数减少歧义。

outer.run outerBlock@{
    inner.run {
        println(this@outerBlock)
        println(this)
    }
}

总结

run 的核心可以记成几句话:

  • run 有两种形式:obj.run { }run { }
  • obj.run { } 里通过 this 访问对象
  • run 返回 Lambda 最后一行结果
  • ?.run { } 适合可空对象非空时计算结果
  • 顶层 run { } 适合创建临时作用域
  • 需要返回对象本身时,使用 apply
  • 只是插入日志或调试时,使用 also

run 的定位是:把一段对象相关逻辑收进作用域里,然后把计算结果作为表达式带出来。

Kotlin with 详解:把已有对象放进作用域集中处理Kotlin with 详解:把已有对象放进作用域集中处理
上一篇
Kotlin with 详解:把已有对象放进作用域集中处理
Kotlin also 详解:附加操作、链式调试与实战示例
下一篇
Kotlin also 详解:附加操作、链式调试与实战示例
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    5038次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4574次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    4522次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    4780次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4731次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码