Kotlin with 详解:把已有对象放进作用域集中处理
时间:2026-08-20 22:58:33 204浏览 收藏
简介
with 是 Kotlin 标准库里的作用域函数。

作用域函数常见有 5 个:
letrunwithapplyalso
with 的定位很清楚:
把一个已有对象传进去,在这个对象的作用域里执行一段逻辑,然后返回 Lambda 最后一行的结果。
常见写法:
val result = with(user) {
"$name-$age"
}
这里的 name、age 来自 user 对象,result 是 Lambda 最后一行的字符串。
with 适合这类场景:
- 对一个已经存在的对象连续读取多个属性
- 对一个已经存在的对象连续调用多个方法
- 在对象作用域里计算并返回结果
- 生成摘要、报表、格式化文本
- 测试代码中集中操作一个对象
with 的源码结构
Kotlin 标准库里,with 大致可以理解成这样:
public inline fun with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
这段代码有几个关键点:
receiver: T:传入的目标对象block: T.() -> R:带接收者的 Lambdareceiver.block():在目标对象作用域里执行 LambdaR:Lambda 的返回值类型
所以 with 的重点是两个:
Lambda 里用 this 访问对象 with 返回 Lambda 最后一行结果
with 不是扩展函数
with 和 run 很像,但调用方式不同。
run 是扩展函数:
val result = user.run {
"$name-$age"
}
with 是普通函数:
val result = with(user) {
"$name-$age"
}
区别可以概括成:
run:对象.run { }
with:with(对象) { }
因为 with 不是扩展函数,所以不能写成:
// 错误写法
// user.with { }
也不能写成:
// 错误写法
// user?.with { }
可空对象更常用 ?.run { },后面会单独说明。
基础用法
先定义一个简单的数据类:
data class User(
val name: String,
val age: Int,
val city: String
)
普通写法:
fun main() {
val user = User("Tom", 18, "上海")
val info = "${user.name}-${user.age}-${user.city}"
println(info)
}
使用 with:
fun main() {
val user = User("Tom", 18, "上海")
val info = with(user) {
"$name-$age-$city"
}
println(info)
}
输出:
Tom-18-上海
在 with(user) { } 里面,this 就是 user。
下面两种写法等价:
val info = with(user) {
"$name-$age-$city"
}
val info = with(user) {
"${this.name}-${this.age}-${this.city}"
}
this 通常可以省略。
with 的返回值
with 返回 Lambda 最后一行结果。
fun main() {
val user = User("Tom", 18, "上海")
val nextAge = with(user) {
age + 1
}
println(nextAge)
}
输出:
19
再看一个多行示例:
fun main() {
val user = User("Tom", 18, "上海")
val result = with(user) {
val adult = age >= 18
"name=$name, city=$city, adult=$adult"
}
println(result)
}
输出:
name=Tom, city=上海, adult=true
最后一行:
"name=$name, city=$city, adult=$adult"
就是整个 with 的返回值。
with 和 run 的关系
with 可以理解成“普通函数版本的 run”。
两者都使用 this,都返回 Lambda 结果。
val result1 = user.run {
"$name-$age"
}
val result2 = with(user) {
"$name-$age"
}
输出结果一样。
主要区别:
| 函数 | 调用方式 | 是否扩展函数 | 可空对象常见写法 |
|---|---|---|---|
run | obj.run { } | 是 | obj?.run { } |
with | with(obj) { } | 否 | 先判空,或改用 ?.run { } |
当对象已经明确非空,并且需要集中访问它的成员时,with 表达很直接。
对象可能为空时,?.run { } 通常更顺手。
可空对象和 with
with 不是扩展函数,所以没有 obj?.with { } 这种写法。
可空对象可以先判空:
fun printUser(user: User?) {
if (user != null) {
with(user) {
println(name)
println(age)
}
}
}
也可以使用 ?.run { }:
fun printUser(user: User?) {
user?.run {
println(name)
println(age)
}
}
with(user) 本身可以接收可空对象,但 Lambda 内的接收者类型也会是可空类型。
fun printNullableUser(user: User?) {
with(user) {
println(this?.name)
}
}
这段代码能运行,但多数情况下不如先判空或使用 ?.run { } 清楚。
和 apply 的区别
with 和 apply 都使用 this。
区别在返回值和调用意图:
| 函数 | 调用方式 | 返回值 | 常见用途 |
|---|---|---|---|
with | with(obj) { } | Lambda 结果 | 集中操作已有对象并返回结果 |
apply | obj.apply { } | 对象本身 | 初始化、配置对象 |
示例:
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 = with(config) {
"$host:$port"
}
println(address)
}
输出:
127.0.0.1:8080
apply 返回配置好的对象,with 返回基于这个对象计算出来的字符串。
和 let 的区别
with 和 let 都能返回 Lambda 结果。
区别在于访问对象的方式。
| 函数 | 上下文对象 | 返回值 | 常见用途 |
|---|---|---|---|
with | this | Lambda 结果 | 集中访问对象成员 |
let | it | Lambda 结果 | 空安全、链式转换、显式参数 |
示例:
val text1 = with(user) {
"$name-$age-$city"
}
val text2 = user.let {
"${it.name}-${it.age}-${it.city}"
}
字段很多时,with 可以少写 it。
需要明确参数名时,let 更合适:
val text = user.let { currentUser ->
"${currentUser.name}-${currentUser.age}"
}
实战 Demo:生成用户摘要
data class Order(
val id: String,
val amount: Double
)
data class UserSummary(
val id: Long,
val name: String,
val city: String,
val orders: List
)
fun buildUserSummary(user: UserSummary): String {
return with(user) {
val totalAmount = orders.sumOf { it.amount }
val orderCount = orders.size
"用户=$name, 城市=$city, 订单数=$orderCount, 总金额=$totalAmount"
}
}
fun main() {
val user = UserSummary(
id = 1001,
name = "Tom",
city = "上海",
orders = listOf(
Order("A001", 120.0),
Order("A002", 260.0)
)
)
println(buildUserSummary(user))
}
输出:
用户=Tom, 城市=上海, 订单数=2, 总金额=380.0
这里需要多次读取 user 的字段,并返回一段摘要文本,适合使用 with。
实战 Demo:矩形面积和周长
data class Rectangle(
val width: Int,
val height: Int
)
data class RectangleInfo(
val area: Int,
val perimeter: Int
)
fun calculate(rectangle: Rectangle): RectangleInfo {
return with(rectangle) {
RectangleInfo(
area = width * height,
perimeter = (width + height) * 2
)
}
}
fun main() {
val info = calculate(Rectangle(width = 5, height = 10))
println(info)
}
输出:
RectangleInfo(area=50, perimeter=30)
with(rectangle) 让 width、height 可以直接参与计算。
实战 Demo:配置已有对象
with 也能修改已有对象。
data class TextStyle(
var text: String = "",
var size: Int = 14,
var color: String = "black",
var bold: Boolean = false
)
fun main() {
val style = TextStyle()
with(style) {
text = "Kotlin with"
size = 18
color = "blue"
bold = true
}
println(style)
}
输出:
TextStyle(text=Kotlin with, size=18, color=blue, bold=true)
如果是创建对象时顺手配置,apply 更常见:
val style = TextStyle().apply {
text = "Kotlin with"
size = 18
color = "blue"
bold = true
}
如果对象已经存在,with(style) { } 也很自然。
实战 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 buildReportSummary(orders: ListPayOrder>): String {
val reports = orders
.filter { it.paid }
.groupBy { it.city }
.map { (city, cityOrders) ->
CityReport(
city = city,
orderCount = cityOrders.size,
totalAmount = cityOrders.sumOf { it.amount }
)
}
.sortedByDescending { it.totalAmount }
return with(reports) {
val totalCount = sumOf { it.orderCount }
val totalAmount = sumOf { it.totalAmount }
val cityNames = joinToString { it.city }
"城市=$cityNames, 订单数=$totalCount, 总金额=$totalAmount"
}
}
fun main() {
println(buildReportSummary(orders))
}
输出:
城市=上海, 深圳, 北京, 订单数=4, 总金额=860.0
reports是一个确定存在的列表,之后要围绕它计算多个值,with(reports)能将这段汇总逻辑集中在一起。
实战 Demo:用 StringBuilder 生成文本
data class Article(
val title: String,
val author: String,
val tags: List
)
fun buildMarkdown(article: Article): String {
return with(StringBuilder()) {
appendLine("# ${article.title}")
appendLine()
appendLine("作者:${article.author}")
appendLine()
appendLine("标签:${article.tags.joinToString()}")
toString()
}
}
fun main() {
val article = Article(
title = "Kotlin with",
author = "Tom",
tags = listOf("Kotlin", "Scope Function")
)
println(buildMarkdown(article))
}
输出:
# Kotlin with 作者:Tom 标签:Kotlin, Scope Function
这里的接收者是 StringBuilder,在 with 代码块里可以连续调用 appendLine。
最后一行 toString() 是返回值。
实战 Demo:测试代码里的集中操作
下面示例展示测试代码里常见的风格。
class ShoppingCart {
private val items = mutableListOf()
fun add(item: String) {
items.add(item)
}
fun remove(item: String) {
items.remove(item)
}
fun size(): Int {
return items.size
}
fun contains(item: String): Boolean {
return items.contains(item)
}
}
fun main() {
val cart = ShoppingCart()
val result = with(cart) {
add("Book")
add("Pen")
remove("Pen")
contains("Book") && size() == 1
}
println(result)
}
输出:
true
with(cart) 把多个购物车操作放在同一个对象作用域里,最后返回断言结果。
嵌套 with 和 this 指向
嵌套 with 时,this 会指向最近一层接收者。
class Address {
var city: String = ""
var street: String = ""
}
class Customer {
var name: String = ""
var address: Address = Address()
}
fun main() {
val customer = Customer()
with(customer) {
name = "Tom"
with(address) {
city = "上海"
street = "人民路"
}
}
println(customer.name)
println(customer.address.city)
}
外层 with(customer) 里,this 是 Customer。
内层 with(address) 里,this 是 Address。
如果需要在内层访问外层对象,可以使用标签:
fun main() {
val customer = Customer()
with(customer) customerBlock@{
name = "Tom"
with(address) {
city = "上海"
street = "${this@customerBlock.name} 的地址"
}
}
println(customer.address.street)
}
输出:
Tom 的地址
with、run、apply、let、also 的区别
作用域函数主要看两个问题:
Lambda 里怎么访问对象 函数返回什么
| 函数 | 上下文对象 | 返回值 | 调用方式 | 常见用途 |
|---|---|---|---|---|
with | this | Lambda 结果 | with(obj) { } | 已有对象集中操作 |
run | this | Lambda 结果 | obj.run { } | 对象作用域里计算结果 |
let | it | Lambda 结果 | obj.let { } | 空安全、链式转换 |
apply | this | 对象本身 | obj.apply { } | 初始化、配置对象 |
also | it | 对象本身 | obj.also { } | 日志、调试、附加动作 |
简单选择方式:
已有非空对象,集中访问成员并返回结果:with 对象可能为空,需要安全调用:run 需要初始化或配置对象并返回对象本身:apply 需要用显式参数名做转换:let 需要插入日志或附加动作:also
常见注意点
with 返回最后一行,不返回对象本身
fun main() {
val user = User("Tom", 18, "上海")
val result = with(user) {
age + 1
}
println(result)
}
输出:
19
如果需要返回对象本身,使用 apply。
val user = User("Tom", 18, "上海").apply {
println(name)
}
with 不支持 obj?.with 形式
val user: User? = null
// 错误写法
// user?.with {
// println(name)
// }
可空对象更常见的写法是:
user?.run {
println(name)
}
with 适合已有对象,不适合强行包裹所有逻辑
val result = with(user) {
"$name-$age"
}
这种写法很自然。
如果代码块和某个对象关系不强,顶层 run { } 可能更清楚:
val result = run {
val a = 10
val b = 20
a + b
}
嵌套 with 需要留意 this
with(customer) {
with(address) {
// this 是 address
}
}
嵌套层级增加后,可以使用标签,或者提取函数减少歧义。
总结
with 的核心可以记成几句话:
with是普通函数,不是扩展函数- 调用方式是
with(obj) { } - Lambda 内部通过
this访问传入对象 with返回 Lambda 最后一行结果with适合对已有非空对象集中操作- 可空对象通常使用
?.run { } - 需要返回对象本身时,使用
apply
with 的定位是:将一个现存的对象置于作用域内,围绕它进行一段集中的操作,最后返回结果。
-
391 收藏
-
415 收藏
-
269 收藏
-
309 收藏
-
444 收藏
-
306 收藏
-
311 收藏
-
297 收藏
-
445 收藏
-
445 收藏
-
452 收藏
-
129 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习