当前位置:首页 > 文章列表 > 文章 > 软件教程 > Kotlin with 详解:把已有对象放进作用域集中处理

Kotlin with 详解:把已有对象放进作用域集中处理

2026-08-20 22:58:33 0浏览 收藏

简介

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

Kotlin with 详解:把已有对象放进作用域集中处理

作用域函数常见有 5 个:

  • let
  • run
  • with
  • apply
  • also

with 的定位很清楚:

把一个已有对象传进去,在这个对象的作用域里执行一段逻辑,然后返回 Lambda 最后一行的结果。

常见写法:

val result = with(user) {
    "$name-$age"
}

这里的 nameage 来自 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:带接收者的 Lambda
  • receiver.block():在目标对象作用域里执行 Lambda
  • R:Lambda 的返回值类型

所以 with 的重点是两个:

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

with 不是扩展函数

withrun 很像,但调用方式不同。

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"
}

输出结果一样。

主要区别:

函数调用方式是否扩展函数可空对象常见写法
runobj.run { }obj?.run { }
withwith(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 的区别

withapply 都使用 this

区别在返回值和调用意图:

函数调用方式返回值常见用途
withwith(obj) { }Lambda 结果集中操作已有对象并返回结果
applyobj.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 的区别

withlet 都能返回 Lambda 结果。

区别在于访问对象的方式。

函数上下文对象返回值常见用途
withthisLambda 结果集中访问对象成员
letitLambda 结果空安全、链式转换、显式参数

示例:

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)widthheight 可以直接参与计算。

实战 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) 里,thisCustomer

内层 with(address) 里,thisAddress

如果需要在内层访问外层对象,可以使用标签:

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 里怎么访问对象
函数返回什么
函数上下文对象返回值调用方式常见用途
withthisLambda 结果with(obj) { }已有对象集中操作
runthisLambda 结果obj.run { }对象作用域里计算结果
letitLambda 结果obj.let { }空安全、链式转换
applythis对象本身obj.apply { }初始化、配置对象
alsoit对象本身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语言做的科学转换计算器
上一篇
C语言做的科学转换计算器
Kotlin run 详解:把对象操作收进作用域,再把结果带出来
下一篇
Kotlin run 详解:把对象操作收进作用域,再把结果带出来
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码