当前位置:首页 > 文章列表 > 文章 > 软件教程 > 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"
}

这里的 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:带接收者的 Lambda
  • receiver.block():在目标对象作用域里执行 Lambda
  • R: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"
}

输出结果一样。

主要区别:

函数调用方式是否扩展函数可空对象常见写法
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 的区别

with 和 apply 都使用 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 的区别

with 和 let 都能返回 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) 让 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 里怎么访问对象
函数返回什么
函数上下文对象返回值调用方式常见用途
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推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    342次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    398次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    392次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    355次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    181次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码