当前位置:首页 > 文章列表 > 文章 > 软件教程 > Kotlin apply 详解:对象初始化、链式配置与实战示例

Kotlin apply 详解:对象初始化、链式配置与实战示例

2026-08-20 23:01:31 0浏览 收藏

简介

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

Kotlin apply 详解:对象初始化、链式配置与实战示例

作用域函数常见有 5 个:

  • let
  • run
  • with
  • apply
  • also

apply 主要用于对象初始化、属性赋值、链式配置。

一句话概括:

apply 会在对象自己的作用域里执行一段代码,执行完成后返回对象本身。

常见写法:

val user = User().apply {
    name = "Tom"
    age = 18
}

这段代码的含义是:

创建 User 对象
进入 User 对象的作用域
设置 name 和 age
返回这个 User 对象

apply 的源码结构

Kotlin 标准库里,apply 大致可以理解成这样:

public inline fun  T.apply(block: T.() -> Unit): T {
    block()
    return this
}

这段代码有几个关键点:

  • T:调用 apply 的对象类型
  • block: T.() -> Unit:带接收者的 Lambda
  • block():在当前对象作用域里执行 Lambda
  • return this:返回当前对象本身

所以 apply 的重点是两个:

Lambda 里用 this 访问对象
apply 返回对象本身

基础用法

先定义一个简单的数据类:

data class User(
    var name: String = "",
    var age: Int = 0,
    var city: String = ""
)

普通写法:

fun main() {
    val user = User()
    user.name = "Tom"
    user.age = 18
    user.city = "上海"

    println(user)
}

使用 apply

fun main() {
    val user = User().apply {
        name = "Tom"
        age = 18
        city = "上海"
    }

    println(user)
}

输出:

User(name=Tom, age=18, city=上海)

apply 代码块里,可以直接访问 User 的属性。

下面两种写法等价:

val user = User().apply {
    name = "Tom"
    age = 18
}
val user = User().apply {
    this.name = "Tom"
    this.age = 18
}

this 通常可以省略。

apply 的返回值

apply 返回的是调用对象本身。

fun main() {
    val user = User().apply {
        name = "Tom"
        age = 18
        "最后一行字符串"
    }

    println(user)
}

输出:

User(name=Tom, age=18, city=)

虽然 Lambda 最后一行是字符串:

"最后一行字符串"

apply 不会返回这个字符串,而是返回 User 对象。

这是 applylet 很重要的区别:

val length = "Kotlin".let {
    it.length
}

let 返回 Lambda 最后一行结果,也就是 6

val value = "Kotlin".apply {
    uppercase()
}

apply 返回原对象,也就是 "Kotlin"

this 接收者

apply 的 Lambda 类型是:

T.() -> Unit

这叫带接收者的 Lambda。

普通 Lambda:

(User) -> Unit

内部一般通过参数访问对象:

val block: (User) -> Unit = { user ->
    user.name = "Tom"
}

带接收者的 Lambda:

User.() -> Unit

内部可以直接访问 User 的成员:

val block: User.() -> Unit = {
    name = "Tom"
}

apply 就是使用这种形式,所以代码块里可以直接写属性名和方法名。

对象初始化

apply 适合创建对象后连续设置多个属性。

data class ServerConfig(
    var host: String = "",
    var port: Int = 0,
    var timeoutSeconds: Int = 0,
    var debug: Boolean = false
)

fun main() {
    val config = ServerConfig().apply {
        host = "127.0.0.1"
        port = 8080
        timeoutSeconds = 30
        debug = true
    }

    println(config)
}

输出:

ServerConfig(host=127.0.0.1, port=8080, timeoutSeconds=30, debug=true)

这种写法把对象创建和对象配置放在一起,适合配置项比较集中的场景。

集合初始化

可变集合初始化时,也可以使用 apply

fun main() {
    val languages = mutableListOf().apply {
        add("Ja va")
        add("Kotlin")
        add("Go")
    }

    println(languages)
}

输出:

[Ja va, Kotlin, Go]

Map 也一样:

fun main() {
    val scores = mutableMapOfInt>().apply {
        put("Kotlin", 100)
        put("Ja va", 90)
        put("Go", 85)
    }

    println(scores)
}

可能输出:

{Kotlin=100, Ja va=90, Go=85}

链式配置

因为 apply 返回对象本身,所以可以继续链式调用。

data class Product(
    var name: String = "",
    var price: Double = 0.0,
    var enabled: Boolean = true
)

fun main() {
    val product = Product()
        .apply {
            name = "Kotlin 入门课程"
            price = 99.0
        }
        .apply {
            enabled = price > 0
        }

    println(product)
}

输出:

Product(name=Kotlin 入门课程, price=99.0, enabled=true)

多个 apply 可以串起来,不过如果配置逻辑属于同一阶段,放在一个 apply 里通常更清楚。

配合 also

applyalso 都返回对象本身。

区别是:

  • apply 通过 this 访问对象,适合配置对象
  • also 通过 it 访问对象,适合日志、调试、附加动作

示例:

data class Order(
    var id: String = "",
    var amount: Double = 0.0,
    var status: String = "CREATED"
)

fun main() {
    val order = Order()
        .apply {
            id = "A001"
            amount = 199.0
            status = "PAID"
        }
        .also {
            println("订单创建完成: ${it.id}, 金额=${it.amount}")
        }

    println(order)
}

输出:

订单创建完成: A001, 金额=199.0
Order(id=A001, amount=199.0, status=PAID)

这里 apply 负责设置属性,also 负责打印日志。

配合可空对象

apply 可以和安全调用符 ?. 配合使用。

data class Profile(
    var nickname: String = "",
    var email: String = "",
    var verified: Boolean = false
)

fun loadProfile(id: Int): Profile? {
    return if (id == 1) Profile() else null
}

fun main() {
    val profile = loadProfile(1)?.apply {
        nickname = "Tom"
        email = "tom@example.com"
        verified = true
    }

    println(profile)
}

输出:

Profile(nickname=Tom, email=tom@example.com, verified=true)

如果 loadProfile 返回 nullapply 不会执行,最终结果也是 null

apply 不适合做值转换

apply 返回对象本身,不返回 Lambda 最后一行。

fun main() {
    val result = "Kotlin".apply {
        length
    }

    println(result)
}

输出:

Kotlin

如果目标是拿到长度,可以使用 letrun

val length1 = "Kotlin".let {
    it.length
}

val length2 = "Kotlin".run {
    length
}

apply 更适合“配置这个对象,然后继续得到这个对象”。

实战 Demo:构建请求对象

后端接口、HTTP 请求、数据库查询,经常需要先组装一个对象。

data class SearchRequest(
    var keyword: String = "",
    var pageIndex: Int = 1,
    var pageSize: Int = 20,
    var sortBy: String = "createdAt",
    var descending: Boolean = true
)

fun buildSearchRequest(keyword: String?, page: Int?): SearchRequest {
    return SearchRequest().apply {
        this.keyword = keyword?.trim().orEmpty()
        pageIndex = page?.takeIf { it > 0 } ?: 1
        pageSize = 20
        sortBy = "createdAt"
        descending = true
    }
}

fun main() {
    val request = buildSearchRequest("  kotlin  ", 2)
    println(request)
}

输出:

SearchRequest(keyword=kotlin, pageIndex=2, pageSize=20, sortBy=createdAt, descending=true)

apply 把请求对象的组装过程放在一个作用域里,返回值仍然是 SearchRequest

实战 Demo:订单创建

data class OrderItem(
    val sku: String,
    val quantity: Int,
    val price: Double
)

data class CreateOrderCommand(
    var userId: Long = 0,
    var items: MutableList = mutableListOf(),
    var totalAmount: Double = 0.0,
    var remark: String = ""
)

fun createOrderCommand(userId: Long, items: ListOrderItem>, remark: String?): CreateOrderCommand {
    return CreateOrderCommand().apply {
        this.userId = userId
        this.items.addAll(items)
        totalAmount = items.sumOf { it.quantity * it.price }
        this.remark = remark?.trim().orEmpty()
    }
}

fun main() {
    val command = createOrderCommand(
        userId = 1001,
        items = listOf(
            OrderItem("BOOK", 2, 59.0),
            OrderItem("PEN", 3, 5.0)
        ),
        remark = "  尽快发货  "
    )

    println(command)
}

输出:

CreateOrderCommand(userId=1001, items=[OrderItem(sku=BOOK, quantity=2, price=59.0), OrderItem(sku=PEN, quantity=3, price=5.0)], totalAmount=133.0, remark=尽快发货)

这里需要设置多个字段,还要计算总金额。apply 能让对象构建过程集中在一起。

实战 Demo:配置对象校验

apply 也可以和普通函数配合,在配置完成后做校验。

data class DatabaseConfig(
    var host: String = "",
    var port: Int = 3306,
    var username: String = "",
    var password: String = "",
    var database: String = ""
)

fun DatabaseConfig.validate(): DatabaseConfig {
    require(host.isNotBlank()) { "host 不能为空" }
    require(port in 1..65535) { "port 不合法" }
    require(username.isNotBlank()) { "username 不能为空" }
    require(database.isNotBlank()) { "database 不能为空" }
    return this
}

fun main() {
    val config = DatabaseConfig()
        .apply {
            host = "127.0.0.1"
            port = 3306
            username = "root"
            password = "123456"
            database = "demo"
        }
        .validate()

    println(config)
}

输出:

DatabaseConfig(host=127.0.0.1, port=3306, username=root, password=123456, database=demo)

apply 负责配置,validate() 负责校验。职责分开后,代码更容易维护。

实战 Demo:简单 Builder 风格 API

apply 很适合实现简单的 Builder 风格 API。

class DialogConfig {
    var title: String = ""
    var message: String = ""
    var positiveText: String = "确定"
    var negativeText: String = "取消"
    var cancelable: Boolean = true
    var onPositive: (() -> Unit)? = null
}

fun dialog(block: DialogConfig.() -> Unit): DialogConfig {
    return DialogConfig().apply(block)
}

fun main() {
    val config = dialog {
        title = "删除确认"
        message = "确认删除这条记录吗?"
        positiveText = "删除"
        negativeText = "取消"
        cancelable = false
        onPositive = {
            println("执行删除")
        }
    }

    println(config.title)
    println(config.message)
    config.onPositive?.invoke()
}

输出:

删除确认
确认删除这条记录吗?
执行删除

dialog { ... } 内部其实就是:

DialogConfig().apply(block)

这个模式在配置对象、构建 DSL 风格 API 时很常见。

实战 Demo:文件写入配置

下面示例只展示写法,运行时会在当前目录创建文件。

import ja va.io.File
import ja va.nio.charset.Charset

data class FileWriteOptions(
    var charset: String = "UTF-8",
    var append: Boolean = false,
    var createParentDirs: Boolean = true
)

fun writeTextFile(
    path: String,
    content: String,
    options: FileWriteOptions = FileWriteOptions()
) {
    val file = File(path)

    options.apply {
        if (createParentDirs) {
            file.parentFile?.mkdirs()
        }

        if (append) {
            file.appendText(content, Charset.forName(charset))
        } else {
            file.writeText(content, Charset.forName(charset))
        }
    }
}

fun main() {
    val options = FileWriteOptions().apply {
        append = false
        createParentDirs = true
    }

    writeTextFile("output/apply-demo.txt", "hello apply", options)
}

options.apply { ... }中读取配置项,在FileWriteOptions().apply { ... }中设置配置项。

嵌套 apply 和 this 指向

嵌套 apply 时,this 容易指向内层对象。

class Address {
    var city: String = ""
    var street: String = ""
}

class Customer {
    var name: String = ""
    var address: Address = Address()
}

fun main() {
    val customer = Customer().apply {
        name = "Tom"

        address.apply {
            city = "上海"
            street = "人民路"
        }
    }

    println(customer.name)
    println(customer.address.city)
}

在外层 apply 里,thisCustomer

在内层 address.apply 里,thisAddress

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

fun main() {
    val customer = Customer().apply customerBlock@{
        name = "Tom"

        address.apply {
            city = "上海"
            street = "${this@customerBlock.name} 的地址"
        }
    }

    println(customer.address.street)
}

输出:

Tom 的地址

this@customerBlock 表示外层 apply 的接收者,也就是 Customer

自定义 applyIf

有时只想在满足条件时配置对象,可以定义一个扩展函数。

inline fun  T.applyIf(condition: Boolean, block: T.() -> Unit): T {
    if (condition) {
        apply(block)
    }
    return this
}

data class ApiOptions(
    var enableCache: Boolean = false,
    var timeoutSeconds: Int = 10,
    var retryTimes: Int = 0
)

fun main() {
    val debug = true

    val options = ApiOptions()
        .apply {
            timeoutSeconds = 30
        }
        .applyIf(debug) {
            enableCache = false
            retryTimes = 1
        }

    println(options)
}

输出:

ApiOptions(enableCache=false, timeoutSeconds=30, retryTimes=1)

applyIf 保持了 apply 的特点:执行配置后仍然返回对象本身。

apply、let、also、run 的区别

作用域函数主要看两个问题:

Lambda 里怎么访问对象
函数返回什么
函数上下文对象返回值常见用途
applythis对象本身初始化、配置对象
alsoit对象本身日志、调试、附加动作
letitLambda 结果空安全、数据转换
runthisLambda 结果在对象作用域里计算结果
withthisLambda 结果对已有对象集中调用

apply 返回对象本身

val user = User().apply {
    name = "Tom"
}

let 返回 Lambda 结果

val name = User().apply {
    this.name = "Tom"
}.let {
    it.name
}

also 返回对象本身

val user = User().apply {
    name = "Tom"
}.also {
    println(it.name)
}

run 返回 Lambda 结果

val name = User().apply {
    this.name = "Tom"
}.run {
    name
}

简单选择方式:

需要初始化或配置对象:apply
需要返回原对象并顺手做日志:also
需要把对象转换成另一个结果:let
需要在对象作用域里计算一个结果:run

常见注意点

apply 返回原对象,不返回最后一行

fun main() {
    val result = StringBuilder().apply {
        append("Kotlin")
        length
    }

    println(result)
}

输出:

Kotlin

虽然最后一行是 length,但 result 仍然是 StringBuilder

不可变对象调用 apply 不会改变值本身

fun main() {
    val text = "kotlin".apply {
        uppercase()
    }

    println(text)
}

输出:

kotlin

String 是不可变对象,uppercase() 会返回新字符串,但 apply 返回的仍然是原字符串。

如果目标是拿到大写结果,可以写成:

val text = "kotlin".uppercase()

或者:

val text = "kotlin".let {
    it.uppercase()
}

apply 里适合放配置逻辑

val user = User().apply {
    name = "Tom"
    age = 18
}

这类属性设置很适合放在 apply 里。

如果代码块里开始出现大量业务判断、数据库访问、网络请求,提取成普通函数通常更清楚。

嵌套 apply 需要留意 this

outer.apply {
    inner.apply {
        // this 是 inner
    }
}

嵌套层级增加后,可以使用命名函数、局部变量或标签减少歧义。

总结

apply 的核心可以记成几句话:

  • apply 是作用域函数
  • Lambda 内部通过 this 访问调用对象
  • this 通常可以省略
  • apply 返回调用对象本身
  • apply 适合对象初始化、属性赋值、链式配置
  • 需要返回 Lambda 结果时,通常使用 letrun
  • 嵌套 apply 时,需要关注 this 指向

apply 的定位相当明确,它围绕着一个对象进行配置,最终还是返回这个对象。只要目标是对“对象本身进行配置”,apply 通常就是个合适的选择。

Kotlin also 详解:附加操作、链式调试与实战示例Kotlin also 详解:附加操作、链式调试与实战示例
上一篇
Kotlin also 详解:附加操作、链式调试与实战示例
Rust 是如何干掉空指针的
下一篇
Rust 是如何干掉空指针的
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码