Kotlin with 详解:把已有对象放进作用域集中处理
简介
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 的定位是:将一个现存的对象置于作用域内,围绕它进行一段集中的操作,最后返回结果。
C语言做的科学转换计算器
- 上一篇
- C语言做的科学转换计算器
- 下一篇
- Kotlin run 详解:把对象操作收进作用域,再把结果带出来
-
- 文章 · 软件教程 | 46分钟前 | vs code · 软件教程 · VS Code 设置同步 Profiles Settings Sync 扩展同步
- VS Code Profiles 怎么只同步指定扩展和设置
- 345浏览 收藏
-
- 文章 · 软件教程 | 7小时前 |
- VS Code 提示仓库不安全时怎么处理 safe.directory
- 199浏览 收藏
-
- 文章 · 软件教程 | 9小时前 | docker · Context ·
- Docker Context 怎么切换远程守护进程
- 372浏览 收藏
-
- 文章 · 软件教程 | 11小时前 | 命令行 · 效率工具 · github · 开发工具 · 软件教程 · 位置参数 命令别名 GitHub CLI gh alias set shell alias
- GitHub CLI 怎么创建带参数的命令别名
- 404浏览 收藏
-
- 文章 · 软件教程 | 14小时前 | obsidian · 软件教程 · YAML Properties Obsidian 笔记元数据 属性类型 Properties视图
- Obsidian Properties 怎么统一管理笔记元数据
- 378浏览 收藏
-
- 文章 · 软件教程 | 16小时前 | postman · 软件教程 · 接口测试 · 环境变量 初始值 Postman 当前值 Local value Shared value
- Postman 环境变量怎么区分初始值与当前值
- 360浏览 收藏
-
- 文章 · 软件教程 | 19小时前 | CI/CD · gitHub actions · 软件教程 · GitHub GitHub Actions 构件保留时间
- GitHub Actions 怎么设置构件保留时间
- 395浏览 收藏
-
- 文章 · 软件教程 | 21小时前 | git · 软件教程 · Git Git worktree worktree prune
- Git worktree 怎么清理失效工作区记录
- 287浏览 收藏
-
- 文章 · 软件教程 | 23小时前 | docker ·
- Docker Build 缓存挂载怎么复用包管理器下载
- 264浏览 收藏
-
- 文章 · 软件教程 | 1天前 | GitHub Copilot Pull Request Code Review API
- GitHub Copilot Code Review API 怎么提交代码审查请求
- 409浏览 收藏
-
- 文章 · 软件教程 | 1天前 |
- GitHub 新 Dashboard 怎么筛选代理会话与拉取请求
- 111浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 342次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 398次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 392次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 355次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 181次使用
-
- VS Code 怎么给 Go 项目配置测试任务:tasks.json 运行与结果验收
- 2026-07-09 501浏览
-
- Windows 11 如何开启 HEIF 图片支持
- 2026-05-31 501浏览
-
- TikTok用户画像与付费订阅变现方法
- 2026-05-27 501浏览
-
- 学信网学历翻译件申请方法
- 2026-05-27 501浏览
-
- Windows 11 24H2 更新失败0x80070005解决方法
- 2026-05-26 501浏览

