2-2-23 快速掌握Kotlin-apply函数详解

Kotlin apply 函数详解

apply 是 Kotlin 标准库中的一个作用域函数(scope function),用于在对象上执行一个代码块,并返回该对象本身。

基本概念

函数签名

kotlin 复制代码
inline fun <T> T.apply(block: T.() -> Unit): T

核心特性

  1. 接收者对象 :在 lambda 内部,this 指向调用 apply 的对象
  2. 返回值 :返回对象本身(返回 this
  3. 典型用途:对象配置和初始化

基本使用

1. 对象初始化

kotlin 复制代码
class Person {
    var name: String = ""
    var age: Int = 0
    var city: String = ""
    
    override fun toString(): String = "Person(name='$name', age=$age, city='$city')"
}

fun main() {
    val person = Person().apply {
        name = "Alice"  // this.name = "Alice"
        age = 25
        city = "New York"
    }
    
    println(person)  // Person(name='Alice', age=25, city='New York')
}

2. 与构造器配合使用

kotlin 复制代码
class User(val id: String) {
    var name: String = ""
    var email: String = ""
    
    fun display() = "User(id=$id, name=$name, email=$email)"
}

fun main() {
    val user = User("123").apply {
        name = "Bob"
        email = "bob@example.com"
    }
    
    println(user.display())
}

apply 与普通初始化对比

不使用 apply

kotlin 复制代码
val person = Person()
person.name = "Alice"
person.age = 25
person.city = "New York"

使用 apply

kotlin 复制代码
val person = Person().apply {
    name = "Alice"
    age = 25
    city = "New York"
}

优势

  • 代码更紧凑
  • 避免重复书写对象变量名
  • 形成逻辑上的代码块

实际应用场景

1. Android 开发中的视图配置

kotlin 复制代码
val textView = TextView(context).apply {
    text = "Hello, Kotlin!"
    textSize = 16f
    setTextColor(Color.BLACK)
    gravity = Gravity.CENTER
    setPadding(16, 16, 16, 16)
}

2. 集合和容器的初始化

kotlin 复制代码
// 初始化列表
val numbers = mutableListOf<Int>().apply {
    for (i in 1..10) add(i * i)
    addAll(listOf(100, 121, 144))
    sortDescending()
}

// 初始化 Map
val config = mutableMapOf<String, Any>().apply {
    put("host", "localhost")
    put("port", 8080)
    put("timeout", 30)
    put("retry", true)
}

3. Builder 模式替代方案

kotlin 复制代码
data class Car(
    val brand: String,
    val model: String,
    val year: Int,
    var color: String = "White",
    var price: Double = 0.0
)

val myCar = Car("Tesla", "Model 3", 2023).apply {
    color = "Red"
    price = 45000.0
}

4. 复杂对象的链式配置

kotlin 复制代码
class DatabaseConfig {
    var host: String = "localhost"
    var port: Int = 3306
    var username: String = "root"
    var password: String = ""
    var database: String = "test"
    
    override fun toString(): String {
        return "DatabaseConfig(host='$host', port=$port, username='$username', database='$database')"
    }
}

fun main() {
    val config = DatabaseConfig().apply {
        host = "192.168.1.100"
        port = 5432
        username = "admin"
        password = "secret123"
        database = "production"
    }
    
    println(config)
}

apply 与其他作用域函数对比

函数 接收者引用 返回值 典型用途
apply this 上下文对象 对象配置
let it Lambda 结果 空检查、转换
run this Lambda 结果 对象计算、链式调用
with this Lambda 结果 非扩展函数,在对象上操作
also it 上下文对象 附加操作、日志记录

对比示例

kotlin 复制代码
class User(var name: String, var age: Int)

fun main() {
    val user = User("Alice", 25)
    
    // apply - 返回对象本身
    val applyResult = user.apply {
        name = "Bob"
        age = 30
    }  // 返回 user 对象
    
    // let - 返回 lambda 结果
    val letResult = user.let {
        it.name = "Charlie"
        it.age = 35
        "Name changed"  // 返回字符串
    }  // 返回 "Name changed"
    
    // also - 返回对象本身
    val alsoResult = user.also {
        it.name = "David"
        it.age = 40
    }  // 返回 user 对象
    
    // run - 返回 lambda 结果
    val runResult = user.run {
        name = "Eve"
        age = 45
        "User updated"  // 返回字符串
    }  // 返回 "User updated"
}

链式调用

1. 多级配置

kotlin 复制代码
data class Address(var street: String = "", var city: String = "")
data class Company(var name: String = "", var address: Address = Address())

val company = Company().apply {
    name = "Tech Corp"
    address.apply {
        street = "123 Main St"
        city = "San Francisco"
    }
}

2. 复杂对象构建

kotlin 复制代码
class Order {
    var id: String = ""
    var items = mutableListOf<String>()
    var total: Double = 0.0
    
    fun addItem(item: String, price: Double) {
        items.add(item)
        total += price
    }
}

val order = Order().apply {
    id = "ORD-001"
    addItem("Laptop", 999.99)
    addItem("Mouse", 29.99)
    addItem("Keyboard", 89.99)
}

与空安全结合

1. 安全调用

kotlin 复制代码
class Config {
    var value: String? = null
}

fun processConfig(config: Config?) {
    config?.apply {
        value = "initialized"
        // 这里可以安全地访问 config 的属性和方法
        println("Config processed: $value")
    }
}

2. 替代 if 非空检查

kotlin 复制代码
fun updateUser(user: User?) {
    // 传统方式
    if (user != null) {
        user.name = "Updated"
        user.age = 30
    }
    
    // 使用 apply 的方式
    user?.apply {
        name = "Updated"
        age = 30
    }
}

性能注意事项

1. apply 是内联函数

apply 函数使用了 inline 修饰符,这意味着在编译时,lambda 代码会被内联到调用处,不会产生额外的函数对象开销。

kotlin 复制代码
// 源代码
val result = obj.apply { doSomething() }

// 编译后(大致等效)
val result = obj
obj.doSomething()

2. 与 also 的选择

  • 使用 apply 当需要在 lambda 内访问多个成员时(通过 this
  • 使用 also 当参数名 it 能提供更好可读性时
kotlin 复制代码
// apply - 适合配置多个属性
val user = User().apply {
    name = "Alice"
    age = 25
    email = "alice@example.com"
}

// also - 适合单个操作或日志记录
val user = User().also {
    it.name = "Alice"
    println("User created: ${it.name}")
}

高级用法

1. DSL(领域特定语言)构建

kotlin 复制代码
class HTML {
    private val children = mutableListOf<String>()
    
    fun div(block: Div.() -> Unit) {
        val div = Div().apply(block)
        children.add(div.toString())
    }
    
    override fun toString() = children.joinToString("\n")
}

class Div {
    var id: String = ""
    var className: String = ""
    
    fun p(text: String) = "<p>$text</p>"
    
    override fun toString() = 
        "<div id='$id' class='$className'>${/* 子元素 */}</div>"
}

fun html(block: HTML.() -> Unit): HTML {
    return HTML().apply(block)
}

fun main() {
    val page = html {
        div {
            id = "header"
            className = "main-header"
        }
        div {
            id = "content"
            className = "main-content"
        }
    }
    
    println(page)
}

2. 配置构建器模式

kotlin 复制代码
class ConfigurationBuilder {
    var host: String = "localhost"
    var port: Int = 8080
    var timeout: Int = 30
    var retries: Int = 3
    
    fun build(): Configuration = Configuration(host, port, timeout, retries)
}

data class Configuration(val host: String, val port: Int, val timeout: Int, val retries: Int)

fun createConfig(block: ConfigurationBuilder.() -> Unit): Configuration {
    return ConfigurationBuilder().apply(block).build()
}

fun main() {
    val config = createConfig {
        host = "api.example.com"
        port = 443
        timeout = 60
        retries = 5
    }
    
    println(config)
}

常见陷阱和最佳实践

1. 不要过度嵌套

kotlin 复制代码
// 避免 - 过度嵌套降低可读性
val result = SomeClass().apply {
    property1.apply {
        subProperty.apply {
            // 太深了!
        }
    }
}

// 建议 - 适度使用
val result = SomeClass().apply {
    property1 = configureProperty1()
    property2 = configureProperty2()
}

fun configureProperty1(): PropertyType {
    return PropertyType().apply {
        // 配置逻辑
    }
}

2. 明确返回意图

kotlin 复制代码
// 清晰 - 明确表示这是初始化
val person = Person().apply {
    name = "Alice"
    age = 25
}

// 混淆 - 不要这样使用,会让人疑惑返回值是什么
fun createPerson(): Person {
    return Person().apply {
        name = "Alice"
        age = 25
        // 如果这里添加了 return,会改变返回值!
    }
}

3. 与构造函数参数的区分

kotlin 复制代码
data class Employee(
    val id: String,
    val name: String
) {
    var department: String = ""
    var salary: Double = 0.0
}

// 使用 apply 配置可选属性
val employee = Employee("E001", "John Doe").apply {
    department = "Engineering"
    salary = 75000.0
}

总结

apply 函数是 Kotlin 中非常实用的工具,主要优势包括:

  1. 简化对象初始化:将多个属性设置集中在一个代码块中
  2. 提高代码可读性:形成逻辑上的初始化单元
  3. 支持链式调用:便于构建复杂对象
  4. 类型安全:在 lambda 内部可以直接访问对象的成员
  5. 性能良好:作为内联函数没有运行时开销

使用时机:

  • 当需要配置对象多个属性时
  • 构建复杂对象或 DSL 时
  • 替代传统的 Builder 模式
  • 在 Android 中配置视图组件时

记住:apply 返回的是对象本身,这使得它非常适合用于初始化配置场景,但不适合需要返回其他值的场景。

相关推荐
冬奇Lab2 小时前
MediaCodec 编解码基础:Buffer 队列、状态机与零拷贝的艺术
android·音视频开发·源码阅读
光影少年2 小时前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.2 小时前
MySQL主从复制实战配置详解,以及企业级相关优化说明
android·mysql·adb
simpleeeeee2 小时前
安卓开发者应该知道的 10 个安卓性能优化秘诀
android
simpleeeeee2 小时前
Android 17:API 级别 37 的开发者指南——现在你需要构建什么
android
zhaoyufei1332 小时前
RK3566 EDP屏幕背光闪修改pwm
android·java
simpleeeeee2 小时前
Android 17 正在改写规则——以下是每位开发者都需要了解的内容
android
summerkissyou19872 小时前
Android-Audio-根据音频焦点控制播放
android·audio
brahmsjiang3 小时前
Java类加载机制解析:从JVM启动到双亲委派,再到Android的特殊实现
android·java·jvm
fire-flyer3 小时前
ClickHouse系列(九):慢查询、内存 OOM 与稳定性治理
android·clickhouse