Kotlin apply 函数详解
apply 是 Kotlin 标准库中的一个作用域函数(scope function),用于在对象上执行一个代码块,并返回该对象本身。
基本概念
函数签名
kotlin
inline fun <T> T.apply(block: T.() -> Unit): T
核心特性
- 接收者对象 :在 lambda 内部,
this指向调用apply的对象 - 返回值 :返回对象本身(返回
this) - 典型用途:对象配置和初始化
基本使用
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 中非常实用的工具,主要优势包括:
- 简化对象初始化:将多个属性设置集中在一个代码块中
- 提高代码可读性:形成逻辑上的初始化单元
- 支持链式调用:便于构建复杂对象
- 类型安全:在 lambda 内部可以直接访问对象的成员
- 性能良好:作为内联函数没有运行时开销
使用时机:
- 当需要配置对象多个属性时
- 构建复杂对象或 DSL 时
- 替代传统的 Builder 模式
- 在 Android 中配置视图组件时
记住:apply 返回的是对象本身,这使得它非常适合用于初始化配置场景,但不适合需要返回其他值的场景。