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

相关推荐
STCNXPARM16 小时前
Android camera之硬件架构
android·硬件架构·camera
2501_9445255417 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
松☆19 小时前
Dart 核心语法精讲:从空安全到流程控制(3)
android·java·开发语言
_李小白20 小时前
【Android 美颜相机】第二十三天:GPUImageDarkenBlendFilter(变暗混合滤镜)
android·数码相机
小天源1 天前
银河麒麟 V10(x86_64)离线安装 MySQL 8.0
android·mysql·adb·麒麟v10
2501_915921431 天前
傻瓜式 HTTPS 抓包,简单抓取iOS设备数据
android·网络协议·ios·小程序·https·uni-app·iphone
csj501 天前
安卓基础之《(20)—高级控件(2)列表类视图》
android
JMchen1231 天前
Android计算摄影实战:多帧合成、HDR+与夜景算法深度剖析
android·经验分享·数码相机·算法·移动开发·android-studio
恋猫de小郭1 天前
Flutter 在 Android 出现随机字体裁剪?其实是图层合并时的边界计算问题
android·flutter·ios
2501_915918411 天前
把 iOS 性能监控融入日常开发与测试流程的做法
android·ios·小程序·https·uni-app·iphone·webview