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 返回的是对象本身,这使得它非常适合用于初始化配置场景,但不适合需要返回其他值的场景。

相关推荐
2501_916007474 小时前
iOS 证书如何创建,从能生成到能长期使用
android·macos·ios·小程序·uni-app·cocoa·iphone
Just_Paranoid5 小时前
【AOSP】Android Dump 信息快速定位方法
android·adb·framework·service·aosp·dumpsys
帅得不敢出门5 小时前
MTK Android11获取真实wifi mac地址
android·mtk
成都大菠萝5 小时前
2-2-16 快速掌握Kotlin-泛型扩展函数
android
I'm Jie5 小时前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven
BoomHe5 小时前
Android 13 (API 33)开发自己的 Settings ,如何配置 WiFi BT 权限
android
城东米粉儿5 小时前
ConcurrentHashMap实现原理 笔记
android
佳哥的技术分享6 小时前
System.setProperty vs adb setprop (Android SystemProperties)
android·adb
Railshiqian7 小时前
通过adb命令获取某个window或View/子View的绘制内容并输出为png图片的方法
android·adb·dump view