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

作者:唐青枫日期:2026/6/12

简介

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

作用域函数常见有 5 个:

  • let
  • run
  • with
  • apply
  • also

run 的特点比较鲜明:

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

常见写法:

1val result = user.run {
2    "$name-$age"
3}
4

这里的 nameage 来自 user 对象,result 是 Lambda 最后一行的字符串。

run 适合这类场景:

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

run 有两种形式

Kotlin 里有两种 run

对象扩展函数 run

1val result = obj.run {
2    // this  obj
3    // 最后一行作为返回值
4}
5

源码大致可以理解成:

1public inline fun <T, R> T.run(block: T.() -> R): R {
2    return block()
3}
4

特点:

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

顶层函数 run

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

源码大致可以理解成:

1public inline fun <R> run(block: () -> R): R {
2    return block()
3}
4

特点:

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

基础用法

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

1data class User(
2    val name: String,
3    val age: Int
4)
5

使用 run

1fun main() {
2    val user = User("Tom", 18)
3
4    val info = user.run {
5        "$name-$age"
6    }
7
8    println(info)
9}
10

输出:

1Tom-18
2

run 代码块里,this 就是 user

下面两种写法等价:

1val info = user.run {
2    "$name-$age"
3}
4
1val info = user.run {
2    "${this.name}-${this.age}"
3}
4

this 通常可以省略。

run 的返回值

run 返回 Lambda 最后一行结果。

1fun main() {
2    val user = User("Tom", 18)
3
4    val nextAge = user.run {
5        age + 1
6    }
7
8    println(nextAge)
9}
10

输出:

119
2

再看一个多行示例:

1fun main() {
2    val user = User("Tom", 18)
3
4    val result = user.run {
5        val adult = age >= 18
6        "name=$name, adult=$adult"
7    }
8
9    println(result)
10}
11

输出:

1name=Tom, adult=true
2

最后一行:

1"name=$name, adult=$adult"
2

就是整个 run 的返回值。

this 接收者

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

1T.() -> R
2

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

1data class Product(
2    val name: String,
3    val price: Double,
4    val discountRate: Double
5)
6
7fun main() {
8    val product = Product("Keyboard", 300.0, 0.8)
9
10    val finalPrice = product.run {
11        price * discountRate
12    }
13
14    println(finalPrice)
15}
16

输出:

1240.0
2

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

1val finalPrice = product.let {
2    it.price * it.discountRate
3}
4

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

和 let 的区别

runlet 都返回 Lambda 结果。

区别在于上下文对象:

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

示例:

1val user = User("Tom", 18)
2
3val a = user.run {
4    "$name-$age"
5}
6
7val b = user.let {
8    "${it.name}-${it.age}"
9}
10
11println(a)
12println(b)
13

输出:

1Tom-18
2Tom-18
3

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

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

1val result = user.let { currentUser ->
2    "${currentUser.name}-${currentUser.age}"
3}
4

和 apply 的区别

runapply 都使用 this

区别在于返回值。

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

示例:

1data class ServerConfig(
2    var host: String = "",
3    var port: Int = 0
4)
5
6fun main() {
7    val config = ServerConfig().apply {
8        host = "127.0.0.1"
9        port = 8080
10    }
11
12    val address = config.run {
13        "$host:$port"
14    }
15
16    println(address)
17}
18

输出:

1127.0.0.1:8080
2

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

可空对象配合 run

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

1data class Address(
2    val city: String,
3    val street: String
4)
5
6data class Customer(
7    val name: String,
8    val address: Address?
9)
10
11fun loadCustomer(id: Int): Customer? {
12    return if (id == 1) {
13        Customer("Tom", Address("上海", "人民路"))
14    } else {
15        null
16    }
17}
18
19fun main() {
20    val addressText = loadCustomer(1)?.run {
21        val cityName = address?.city ?: "未知城市"
22        "$name - $cityName"
23    } ?: "客户不存在"
24
25    println(addressText)
26}
27

输出:

1Tom - 上海
2

含义是:

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

顶层 run:临时作用域

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

1fun main() {
2    val result = run {
3        val a = 10
4        val b = 20
5        a + b
6    }
7
8    println(result)
9}
10

输出:

130
2

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

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

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

1fun formatInput(input: String?): String {
2    return run {
3        val value = input?.trim().orEmpty()
4
5        if (value.isBlank()) {
6            "EMPTY"
7        } else {
8            value.uppercase()
9        }
10    }
11}
12
13fun main() {
14    println(formatInput("  kotlin  "))
15    println(formatInput("   "))
16}
17

输出:

1KOTLIN
2EMPTY
3

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

链式调用中使用 run

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

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

输出:

1平方后的偶数: [4, 16, 36]
256
3

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

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

实战 Demo:订单金额计算

1data class OrderItem(
2    val sku: String,
3    val quantity: Int,
4    val price: Double
5)
6
7data class Order(
8    val id: String,
9    val items: List<OrderItem>,
10    val couponAmount: Double = 0.0
11)
12
13fun calculatePayAmount(order: Order): Double {
14    return order.run {
15        val total = items.sumOf { it.quantity * it.price }
16        val afterCoupon = total - couponAmount
17
18        if (afterCoupon < 0) {
19            0.0
20        } else {
21            afterCoupon
22        }
23    }
24}
25
26fun main() {
27    val order = Order(
28        id = "A001",
29        items = listOf(
30            OrderItem("BOOK", 2, 59.0),
31            OrderItem("PEN", 3, 5.0)
32        ),
33        couponAmount = 20.0
34    )
35
36    println(calculatePayAmount(order))
37}
38

输出:

1113.0
2

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

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

1data class UserProfile(
2    val id: Long,
3    val nickname: String?,
4    val email: String?,
5    val vipLevel: Int
6)
7
8fun buildDisplayName(profile: UserProfile): String {
9    return profile.run {
10        val name = nickname?.takeIf { it.isNotBlank() }
11            ?: email?.substringBefore("@")
12            ?: "用户$id"
13
14        val vipText = if (vipLevel > 0) "VIP$vipLevel" else "普通用户"
15
16        "$name ($vipText)"
17    }
18}
19
20fun main() {
21    val profile = UserProfile(
22        id = 1001,
23        nickname = null,
24        email = "tom@example.com",
25        vipLevel = 2
26    )
27
28    println(buildDisplayName(profile))
29}
30

输出:

1tom (VIP2)
2

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

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

1data class DbConfig(
2    var host: String = "",
3    var port: Int = 3306,
4    var database: String = "",
5    var username: String = ""
6)
7
8fun main() {
9    val jdbcUrl = DbConfig()
10        .apply {
11            host = "127.0.0.1"
12            port = 3306
13            database = "demo"
14            username = "root"
15        }
16        .run {
17            "jdbc:mysql://$host:$port/$database?user=$username"
18        }
19
20    println(jdbcUrl)
21}
22

输出:

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

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

实战 Demo:读取配置并转换

1fun readConfig(key: String): String? {
2    val configs = mapOf(
3        "server.port" to "8080",
4        "server.timeout" to " 30 ",
5        "feature.debug" to "true"
6    )
7
8    return configs[key]
9}
10
11data class ServerOptions(
12    val port: Int,
13    val timeoutSeconds: Int,
14    val debug: Boolean
15)
16
17fun loadOptions(): ServerOptions {
18    return run {
19        val port = readConfig("server.port")?.toIntOrNull() ?: 80
20        val timeout = readConfig("server.timeout")?.trim()?.toIntOrNull() ?: 10
21        val debug = readConfig("feature.debug")?.toBooleanStrictOrNull() ?: false
22
23        ServerOptions(
24            port = port,
25            timeoutSeconds = timeout,
26            debug = debug
27        )
28    }
29}
30
31fun main() {
32    println(loadOptions())
33}
34

输出:

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

顶层 runporttimeoutdebug 这些临时变量只存在于配置加载代码块里。

实战 Demo:报表统计

1data class PayOrder(
2    val id: String,
3    val city: String,
4    val amount: Double,
5    val paid: Boolean
6)
7
8data class CityReport(
9    val city: String,
10    val orderCount: Int,
11    val totalAmount: Double
12)
13
14val orders = listOf(
15    PayOrder("A001", "上海", 120.0, true),
16    PayOrder("A002", "北京", 80.0, false),
17    PayOrder("A003", "上海", 260.0, true),
18    PayOrder("A004", "深圳", 300.0, true),
19    PayOrder("A005", "北京", 180.0, true)
20)
21

需求:

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

代码:

1fun buildSummary(orders: List<PayOrder>): String {
2    return orders
3        .filter { it.paid }
4        .groupBy { it.city }
5        .map { (city, cityOrders) ->
6            CityReport(
7                city = city,
8                orderCount = cityOrders.size,
9                totalAmount = cityOrders.sumOf { it.amount }
10            )
11        }
12        .sortedByDescending { it.totalAmount }
13        .run {
14            val totalOrders = sumOf { it.orderCount }
15            val totalAmount = sumOf { it.totalAmount }
16            val cityNames = joinToString { it.city }
17
18            "城市=$cityNames, 订单数=$totalOrders, 总金额=$totalAmount"
19        }
20}
21
22fun main() {
23    println(buildSummary(orders))
24}
25

输出:

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

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

自定义 runIfNotNull

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

1inline fun <T, R> T?.runIfNotNull(block: T.() -> R): R? {
2    return this?.run(block)
3}
4
5data class Account(
6    val id: Long,
7    val email: String?
8)
9
10fun main() {
11    val account: Account? = Account(1, "tom@example.com")
12
13    val domain = account.runIfNotNull {
14        email?.substringAfter("@")
15    }
16
17    println(domain)
18}
19

输出:

1example.com
2

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

run、let、apply、also 的区别

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

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

简单选择方式:

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

run 和 with 的关系

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

区别是调用方式:

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

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

1val result = userOrNull?.run {
2    "$name-$age"
3}
4

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

常见注意点

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

1fun main() {
2    val result = User("Tom", 18).run {
3        age + 1
4    }
5
6    println(result)
7}
8

输出:

119
2

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

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

只做日志时 also 更贴近语义

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

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

顶层 run 不宜包住整段函数

1fun createUser(): User {
2    return run {
3        val name = "Tom"
4        val age = 18
5        User(name, age)
6    }
7}
8

这段代码能运行。

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

1fun createUser(): User {
2    val name = "Tom"
3    val age = 18
4    return User(name, age)
5}
6

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

嵌套 run 需要留意 this

1outer.run {
2    inner.run {
3        // this  inner
4    }
5}
6

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

1outer.run outerBlock@{
2    inner.run {
3        println(this@outerBlock)
4        println(this)
5    }
6}
7

总结

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

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

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


Kotlin run 详解:把对象操作收进作用域,再把结果带出来》 是转载文章,点击查看原文


相关推荐


为什么 Clean Architecture 能让 ViewModel 保持轻量?
潜龙勿用之化骨龙2026/6/5

在 Android 开发中,MVVM 是非常常见的 UI 架构模式。它通过 ViewModel 承接 UI 状态和用户交互,让界面层变得更加清晰。 但在真实项目里,随着业务不断增加,很多团队都会遇到同一个问题: ViewModel 越写越胖,业务判断、网络请求、数据保存、异常处理全都堆在里面。 一开始这样写可能很方便,但当业务规则越来越复杂时,ViewModel 就会逐渐变成一个“业务大杂烩”:难维护、难复用、难扩展。 Clean Architecture 的价值,正是在于帮助我们把这些职责


Git & Linux 速查表
love8888_cnsd2026/5/29

📋 Git & Linux 速查表 — Java 后端向 一、Git 操作速查 🔥 最高频(日常必用) 命令说明git status查看工作区状态(最常用,肌肉记忆)git add .添加所有改动到暂存区git add <file>添加指定文件到暂存区git commit -m "type: 描述"提交到本地仓库(推荐语义化 message)git push推送到远程仓库git pull拉取并合并远程更新(fetch + merge)git log --oneline -10查看最近 1


重构 AI 思维(一):Prompt Engineering,如何下达不可违抗的指令?
码上实战2026/5/7

嘿,兄弟们好,我是飞哥。 前阵子我发了那篇上岸感悟,很多兄弟私信我:“飞哥,你老说现在要靠 AI 铲子吃饭,可我发现这 AI 经常‘不听话’,给的回答不是太虚就是格式乱掉,这铲子不好使啊。” 确实,很多兄弟还把 AI 当成**“搜索引擎”在用——随手甩个问题,等着它给标准答案。但对于咱们要搞生产级应用的 Java 佬来说,你得把它当成一个“初级开发”或者“外包伙计”**。 你给外包下需求,如果只是随口一句“帮我实现个抢票逻辑”,他保准给你搞出一堆 Bug。你得有清晰的文档、明确的边界、严苛的格式


LuatOS 课程-011 讲:GNSS应用开发
上海合宙LuatOS2026/4/27

在物联网项目开发中,智能定位系统是一类常见且实用的应用场景,本文将基于 LuatOS,分享一款智能定位系统的开发思路与相关实现要点。 在实际开发过程中,类似学生卡定位器的需求十分普遍,这类需求通常对定位精度、设备续航能力、轨迹显示效果以及多平台适配性均有明确要求,而基于 LuatOS 的智能定位系统,可针对性解决这类开发需求中的核心痛点。 项目特点: 🛰️ 三合一定位:GNSS + 基站 + WiFi🔋 续航:智能功耗管理,运动才定位🛣️ 轨迹优化:减少80%GPS静态漂移以及运动漂移


OpenClaw 七大扩展组件深度技术解析
稚枭天卓2026/4/19

针对 Plugin、Skill、Tool、MCP、Agent、Command、Hook 七大核心组件进行底层实现维度的拆解。 1. Plugin (插件容器) 1.1 模块基础解析 核心定位: Plugin 是 OpenClaw 生态中的物理分发与逻辑隔离单元。它通过 openclaw.plugin.json 清单文件,将 Tool、Skill、Hook 等零散能力打包成一个可独立安装、版本控制和卸载的 NPM 包或本地目录。 使用场景: 开发者封装特定领域能力(如“飞书集成


M3-markconv库找不到wkhtmltopdf问题
郑恩赐2026/4/11

M3-markconv库找不到wkhtmltopdf问题 📝 摘要 在使用 markconv 进行 PDF 转换时,你可能会遇到 OSError: No wkhtmltopdf executable found 错误。这表示系统没有安装 wkhtmltopdf 工具,只需要安装它就能解决 💪 1. 问题描述 📚 1.1 主要报错 当你运行 PDF 转换代码时,会看到以下关键报错: OSError: No wkhtmltopdf executable found: "C:\\Progra


ToB架构师避坑指南:拒绝过度设计,用ROI思维构建高可用开放平台,一份设计指南
uzong2026/4/3

作者:面汤放盐 | uzong 本文将系统且全面地讨论如何设计一个开放平台,内容涉及布局、设计、踩坑及经验分享等。 面向群体:工程师、技术负责人、架构师等。 1. 开放平台 1.1. 定位清晰、MVP先行、ROI导向 需界定平台是专注于数据开放、能力开放,还是构建综合生态;同时明确目标用户群体,是服务大型企业、中小企业,还是特定行业客户 清晰的定位和画像直接决定了模块建设的优先级、功能深度及技术选型,是后续所有设计决策的基石,必须在方案初期落实。 在此基础上,应遵循 YAGNI(You Ain'


INFINI Labs 产品更新 - Easysearch 2.1.0 新增高性能 Rules 规则引擎插件,数据探索 Discover 等
极限实验室2026/3/25

INFINI Easysearch v2.1.0 发布:新增 Rules 规则引擎(百万级规则、复杂表达式、自动同步恢复)与 形态学分析插件(俄语/英语词形还原,提升搜索召回率);审计日志支持动态用户审计,UI 新增日志查看、配置及数据探索页面,运维更高效。INFINI Console、Gateway、Agent、Loadgen v1.30.3 统一基于 Framework 升级,优化本地磁盘队列数据消费。详情见 Release Notes。 Easysearch v2.1.0 INFINI E


AI辅助开发最佳实践:2026年新方法
牛奶2026/3/17

这是系列第六篇。05篇我们讲了AI批量处理,这篇来看看怎么系统化管理AI配置,让AI真正成为你的开发助手。 上一篇文章,我们讲了怎么用AI批量处理重复工作。 这篇文章,我们来聊聊怎么系统化管理AI配置。 原文地址 墨渊书肆/AI辅助开发最佳实践:2026年新方法 如果你已经用AI辅助开发一段时间,可能会遇到这些问题: 每次都要重复说同样的话 — "用TypeScript"、"注意暗色模式"、"用Tailwind" 好的实践没法传承 — 踩过的坑、学到的技巧,用完就忘了 团队配置不统一


小龙虾(openclaw),轻松玩转自动发帖
万少 VIP.5 如鱼得水2026/3/9

小龙虾(openclaw),轻松玩转自动发帖 万少:华为HDE、鸿蒙极客 个人主页:blog.zbztb.cn/ 2025年参与孵化了20+鸿蒙应用、技术文章300+、鸿蒙知识库用户500+、鸿蒙免费课程2套。 如果你也喜欢交流AI和鸿蒙技术,欢迎扣我。 前言 很多小伙伴说安装了小龙虾之后,不知道可以做一些什么,那么这里可以提供一些好玩的场景,比如自动发帖,这里主要通过稀土掘金来演示。 如果大家还没有安装好小龙虾(openclaw),可以参考这个内容先去安装,也可以联系我寻求帮助。 mp.

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读