Kotlin 作用域函数 & 优雅写法
1. 五大作用域函数概览
| 函数 |
作用域对象引用 |
返回值 |
典型用途 |
let |
it |
块的最后一行 |
空安全执行 / 转换对象 |
run |
this |
块的最后一行 |
在对象上执行块,返回结果 |
apply |
this |
对象本身 (this) |
配置对象属性 |
also |
it |
对象本身 (it) |
额外操作(日志、打印) |
with |
this |
块的最后一行 |
调用多个方法,简化调用 |
2. this / it 区别
data class Person(var name: String = "", var age: Int = 0)
// this:直接访问属性和方法(类似在类内部)
val person1 = Person().run {
this.name = "windCloud" // this 可省略
this.age = 25 // this 可省略
"${this.name} is ${this.age}" // 这里需要 this
}
// it:像普通 lambda 参数
val length = "Kotlin".let {
it.length // it 必须写
}
// it 可重命名为更语义化的名字
val length2 = "Kotlin".let { str ->
str.length
}
3. let --- 空安全 + 转换
核心机制
// 普通 let:可空对象执行块,非空返回块结果,空返回 null
val result: Int? = nullable?.let { it.length }
// let 非空才执行(安全调用 ?. 结合)
var name: String? = null
name?.let { println("Length: ${it.length}") } // 不执行,输出 null
name = "Kotlin"
name?.let { println("Length: ${it.length}") } // 执行,输出 6
实战用法
// 用法一:可空变量安全处理
fun process(input: String?): Int {
return input?.let {
it.trim()
if (it.isNotEmpty()) it.length
else 0
} ?: 0 // input 为 null 时返回 0
}
// 用法二:链式空安全
val avatarUrl = user
?.profile
?.avatar
?.let { it.url }
// 用法三:let + also 执行多个操作
val processed = data
.let { transform(it) }
?.also { saveToCache(it) }
// 用法四:let 作为表达式(替代 if-not-null)
val displayName = name
?.let { it.trim() }
?.takeIf { it.isNotEmpty() }
?: "Anonymous"
// 用法五:let + run 组合
nullableValue?.let { value ->
someFunction(value)
}.run {
// 这里的 this 是前一步的返回结果
anotherFunction(this)
}
4. run --- 执行 + 返回结果
核心机制
// run:this 引用对象,执行块后返回块结果
val result = "Hello".run {
this.uppercase() // this 可省略
} // 返回 "HELLO"
// run 适合:对象上执行多个操作,最终获取某种结果
val info = StringBuilder().run {
append("Name: ")
append("windCloud")
append("\nAge: ")
append("25")
toString()
} // "Name: windCloud\nAge: 25"
实战用法
// 用法一:链式操作,最终获取结果
val url = HttpUrl.Builder()
.run {
addQueryParameter("name", "wind")
addQueryParameter("age", "25")
}
.build()
// 用法二:作用域内配置 + 返回配置后对象
val configuredClient = OkHttpClient.Builder()
.run {
connectTimeout(30, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
writeTimeout(30, TimeUnit.SECONDS)
}
.build()
// 用法三:let + run(先空安全,再执行)
nullablePerson
?.let { person ->
person.name = "Updated"
person
}
?.run { sendNotification(this) }
// 用法四:inline run(执行独立代码块,返回最后结果)
val average = run {
val a = 10
val b = 20
(a + b) / 2.0
} // 15.0
5. apply --- 配置对象
核心机制
// apply:this 引用对象,返回对象本身(用于配置)
val person = Person().apply {
name = "windCloud" // this 引用 person
age = 25 // this 可省略
}
// person 已被配置好,返回 person 本身
实战用法
// 用法一:创建 + 配置一步完成(最常用)
val user = User(
id = 1,
name = "windCloud"
).apply {
email = "wind@example.com"
isActive = true
lastLogin = System.currentTimeMillis()
}
// 用法二:AlertDialog 配置
AlertDialog.Builder(context)
.apply {
setTitle("确认")
setMessage("是否删除?")
setPositiveButton("确定") { _, _ -> delete() }
setNegativeButton("取消", null)
}
.show()
// 用法三:Intent 配置(Android)
val intent = Intent(context, TargetActivity::class.java).apply {
putExtra("userId", userId)
putExtra("username", username)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
// 用法四:RecyclerView ItemDecoration 配置
val decoration = DividerItemDecoration(context, VERTICAL).apply {
setDrawable(ContextCompat.getDrawable(context, R.drawable.divider)!!)
}
recyclerView.addItemDecoration(decoration)
// 用法五:View 配置(Android)
rootView.apply {
setPadding(16, 16, 16, 16)
setBackgroundColor(Color.WHITE)
elevation = 8f
}
6. also --- 额外操作
核心机制
// also:it 引用对象,返回对象本身(用于日志、调试)
val list = mutableListOf(1, 2, 3).also {
println("Created list with items: $it")
}
// 输出: Created list with items: [1, 2, 3]
// 返回 list 本身,继续链式操作
实战用法
// 用法一:创建对象时打印日志
val config = mutableMapOf("debug" to "true")
.also { println("Config created: $it") }
.also { saveConfig(it) }
// 用法二:链式操作中途打印调试
val result = listOf(1, 2, 3, 4, 5)
.map { it * 2 }
.also { println("After map: $it") }
.filter { it > 5 }
.also { println("After filter: $it") }
.take(2)
// 输出:
// After map: [2, 4, 6, 8, 10]
// After filter: [6, 8, 10]
// result: [6, 8]
// 用法三:集合处理前验证
val items = mutableListOf(1, 2, 3)
.also {
require(it.isNotEmpty()) { "List cannot be empty" }
println("Processing ${it.size} items")
}
.map { it * 2 }
// 用法四:保存原始值用于调试
val numbers = listOf(1, 2, 3, 4, 5)
.also { println("Original: $it") }
.map { it * 2 }
.also { println("Doubled: $it") }
7. with --- 调用多个方法(不使用 it/this 引用)
核心机制
// with:this 引用对象,调用多个方法,最后返回块结果
// 注意:with 第一个参数是对象,不是作用域内的 this
val info = with(StringBuilder()) {
append("Line 1\n")
append("Line 2\n")
toString()
} // 返回 "Line 1\nLine 2\n"
// ⚠️ with 无法在空安全场景使用(对象可为 null 时不适用)
实战用法
// 用法一:简化多次调用同一个对象的方法
val text = with(textView) {
text = "Hello"
setTextColor(Color.BLACK)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
text.toString()
}
// 用法二:获取某范围内的结果
val score = with(student) {
name = "windCloud"
age = 25
calculateScore() // 假设返回 Double
}
// 用法三:替代 apply(with 参数放括号内,apply 参数放作用域)
val person1 = Person().apply {
name = "windCloud"
age = 25
}
val person2 = with(Person()) {
this.name = "windCloud"
this.age = 25
this // 如需返回 person 对象本身
}
// 用法四:配置集合内容
val list = with(mutableListOf<String>()) {
add("Alice")
add("Bob")
add("Charlie")
sort()
this // 返回 list
}
8. 实战选择决策树
开始:需要操作对象吗?
│
├─ 否 → 不需要作用域函数,直接操作
│
└─ 是 → 对象是否可为 null?
│
├─ 是 → 使用 ?.let { ... } 或直接用 if-null 检查
│
└─ 否 → 继续:需要返回值吗?
│
├─ 否 → 返回对象本身(配置场景)
│ │
│ ├─ 使用 this 引用属性 → apply
│ └─ 使用 it 引用对象 → also
│
└─ 是 → 返回块最后一行结果
│
├─ 使用 this 引用对象属性 → run
├─ 使用 it 引用对象 → let
└─ 调用多个方法但不想用 lambda 形式 → with
9. Android 实战规范
规范一:创建 + 配置用 apply(推荐)
// ✅ 推荐:apply 语义明确,对象配置
val user = User(id, name).apply {
email = email
isActive = true
lastLogin = now
}
// ❌ 避免:run 配置 + 赋值不清晰
val user = User(id, name).run {
email = email
isActive = true
// run 适合块内计算返回,不适合纯配置
}
规范二:空安全用 let
// ✅ 推荐:let 链式空安全
nullableUser?.let { user ->
saveUser(user)
notifySuccess()
} ?: run {
showError("User is null")
}
// ❌ 避免:嵌套 if-else
if (nullableUser != null) {
saveUser(nullableUser)
} else {
showError("User is null")
}
规范三:打印/日志用 also
// ✅ 推荐:also 明确表示"额外操作"
val result = items
.filter { it.isActive }
.also { println("Active items: ${it.size}") }
.map { it.toDisplay() }
// ❌ 避免:forEach 用于链式日志(forEach 返回 Unit,破坏链)
规范四:Intent/View 配置用 apply
// ✅ Android 标准做法
val intent = Intent(context, DetailActivity::class.java).apply {
putExtra(EXTRA_ID, id)
putExtra(EXTRA_NAME, name)
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
}
// ✅ View 配置
findViewById<Button>(R.id.btnSubmit).apply {
text = "提交"
setOnClickListener { submit() }
isEnabled = true
}
规范五:链式转换用 let
// ✅ 推荐:let 转换对象
val userDisplay = user
?.let { UserDTO(it.id, it.name, it.email) }
?.let { dto -> "${dto.id}: ${dto.name}" }
?: "Unknown User"
// ❌ 避免:过度嵌套 let
规范六:临时作用域用 run
// ✅ 推荐:run 用于临时作用域计算
val bitmap = run {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(res, R.drawable.image, options)
options.outWidth to options.outHeight
}
// ✅ 多步骤配置 + 最终结果
val httpClient = OkHttpClient.Builder()
.run {
connectTimeout(30, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
retryOnConnectionFailure(true)
}
.build()
10. 反面教材 & 避坑
// ❌ 滥用 apply:链式中间配置(apply 返回 this,破坏链式语义)
val user = User(id, name)
.apply { email = email } // apply 返回 User,不适合链中
.also { log("User created: $it") } // also 可以
.apply { lastLogin = now } // 滥用 apply
// ✅ 正确:配置用 apply,链中用 also
val user = User(id, name).apply {
email = email
lastLogin = now
}.also { log("User created: $it") }
// ❌ let + apply 混用(语义冲突)
nullableUser?.let { user ->
user.apply { name = "New" } // let 用 it,apply 用 this,混乱
}
// ✅ 正确:用 run 或直接 let
nullableUser?.run { this.name = "New" }
nullableUser?.let { it.name = "New" }
// ❌ with(null) 不安全
val len = with(nullableStr) { // nullableStr 为 null 时 NPE
length
}
// ✅ 正确:let 替代
val len = nullableStr?.let { it.length } ?: 0
11. 综合示例
// 场景:完整的 ViewModel + Repository 初始化
class MainViewModel(
private val context: Context,
private val userId: String
) : ViewModel() {
private val repository: Repository by lazy {
Repository(
Retrofit.Builder()
.baseUrl(BASE_URL)
.run {
addConverterFactory(GsonConverterFactory.create())
client(buildClient())
}
.build()
)
}
private val uiState: MutableStateFlow<UiState> = MutableStateFlow(UiState.Idle)
fun loadUser() {
viewModelScope.launch {
uiState.value = UiState.Loading
repository.getUser(userId)
.also { println("API response: $it") }
?.let { user ->
user.toUiModel()
}
?.run { uiState.value = UiState.Success(this) }
?: uiState.value = UiState.Error("User not found")
}
}
}
快速对照表
| 场景 |
推荐函数 |
原因 |
| 可空对象安全执行 |
?.let { } |
空安全链 |
| 对象配置(推荐) |
apply |
返回对象本身,语义明确 |
| 链中日志/调试 |
also |
返回自身,保留链 |
| 获取块结果 |
run |
返回最后一行 |
| 调用多个方法 |
with |
括号内传对象 |
| 链式转换 |
let |
返回转换结果 |
| 临时计算块 |
run { } |
不依赖对象 |
记忆口诀:
- 配置对象 → apply
- 额外操作 → also
- 空安全 → let
- 获取结果 → run
- 调用多个方法 → with
- 分不清的时候,想清楚你要返回什么:返回对象本身 用 apply/also,返回块结果用 let/run/with