简介
run 是 Kotlin 标准库里的作用域函数。
作用域函数常见有 5 个:
letrunwithapplyalso
run 的特点比较鲜明:
在对象作用域里执行一段逻辑,然后返回 Lambda 最后一行的结果。
常见写法:
1val result = user.run { 2 "$name-$age" 3} 4
这里的 name、age 来自 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
如果需要频繁访问同一个对象的属性,run 的 this 风格会更紧凑。
和 let 的区别
run 和 let 都返回 Lambda 结果。
区别在于上下文对象:
| 函数 | 上下文对象 | 返回值 | 常见用途 |
|---|---|---|---|
| run | this | Lambda 结果 | 在对象作用域里计算结果 |
| let | it | Lambda 结果 | 空安全、链式转换、显式参数 |
示例:
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 的区别
run 和 apply 都使用 this。
区别在于返回值。
| 函数 | 上下文对象 | 返回值 | 常见用途 |
|---|---|---|---|
| run | this | Lambda 结果 | 计算结果 |
| apply | this | 对象本身 | 初始化、配置对象 |
示例:
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
顶层 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,所以 total 是 Int。
实战 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
顶层 run 让 port、timeout、debug 这些临时变量只存在于配置加载代码块里。
实战 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
| 函数 | 上下文对象 | 返回值 | 常见用途 |
|---|---|---|---|
| run | this | Lambda 结果 | 在对象作用域里计算结果 |
| let | it | Lambda 结果 | 空安全、链式转换、显式参数 |
| apply | this | 对象本身 | 初始化、配置对象 |
| also | it | 对象本身 | 日志、调试、校验、附加动作 |
| with | this | Lambda 结果 | 对已有对象集中调用 |
简单选择方式:
1需要在对象作用域里返回一个结果:run 2需要用显式参数名做转换:let 3需要初始化或配置对象:apply 4需要附加动作并继续返回原对象:also 5
run 和 with 的关系
run 和 with 都使用 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 详解:把对象操作收进作用域,再把结果带出来》 是转载文章,点击查看原文。
