Kotlin 从入门到进阶 之作用域函数 & 优雅写法(五)

Kotlin 作用域函数 & 优雅写法

1. 五大作用域函数概览

函数 作用域对象引用 返回值 典型用途
let it 块的最后一行 空安全执行 / 转换对象
run this 块的最后一行 在对象上执行块,返回结果
apply this 对象本身 (this) 配置对象属性
also it 对象本身 (it) 额外操作(日志、打印)
with this 块的最后一行 调用多个方法,简化调用

2. this / it 区别

kotlin 复制代码
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 --- 空安全 + 转换

核心机制

kotlin 复制代码
// 普通 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

实战用法

kotlin 复制代码
// 用法一:可空变量安全处理
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 --- 执行 + 返回结果

核心机制

kotlin 复制代码
// 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"

实战用法

kotlin 复制代码
// 用法一:链式操作,最终获取结果
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 --- 配置对象

核心机制

kotlin 复制代码
// apply:this 引用对象,返回对象本身(用于配置)
val person = Person().apply {
    name = "windCloud"      // this 引用 person
    age = 25                // this 可省略
}
// person 已被配置好,返回 person 本身

实战用法

kotlin 复制代码
// 用法一:创建 + 配置一步完成(最常用)
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 --- 额外操作

核心机制

kotlin 复制代码
// also:it 引用对象,返回对象本身(用于日志、调试)
val list = mutableListOf(1, 2, 3).also {
    println("Created list with items: $it")
}
// 输出: Created list with items: [1, 2, 3]
// 返回 list 本身,继续链式操作

实战用法

kotlin 复制代码
// 用法一:创建对象时打印日志
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 引用)

核心机制

kotlin 复制代码
// with:this 引用对象,调用多个方法,最后返回块结果
// 注意:with 第一个参数是对象,不是作用域内的 this
val info = with(StringBuilder()) {
    append("Line 1\n")
    append("Line 2\n")
    toString()
}  // 返回 "Line 1\nLine 2\n"

// ⚠️ with 无法在空安全场景使用(对象可为 null 时不适用)

实战用法

kotlin 复制代码
// 用法一:简化多次调用同一个对象的方法
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(推荐)

kotlin 复制代码
// ✅ 推荐: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

kotlin 复制代码
// ✅ 推荐: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

kotlin 复制代码
// ✅ 推荐:also 明确表示"额外操作"
val result = items
    .filter { it.isActive }
    .also { println("Active items: ${it.size}") }
    .map { it.toDisplay() }

// ❌ 避免:forEach 用于链式日志(forEach 返回 Unit,破坏链)

规范四:Intent/View 配置用 apply

kotlin 复制代码
// ✅ 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

kotlin 复制代码
// ✅ 推荐:let 转换对象
val userDisplay = user
    ?.let { UserDTO(it.id, it.name, it.email) }
    ?.let { dto -> "${dto.id}: ${dto.name}" }
    ?: "Unknown User"

// ❌ 避免:过度嵌套 let

规范六:临时作用域用 run

kotlin 复制代码
// ✅ 推荐: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. 反面教材 & 避坑

kotlin 复制代码
// ❌ 滥用 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. 综合示例

kotlin 复制代码
// 场景:完整的 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
相关推荐
Vallelonga17 小时前
Rust 中 unsafe 关键字的语义
开发语言·rust
AI砖家17 小时前
前端 JavaScript 异步处理全方案详解:从回调到 Observable
开发语言·前端·javascript
思麟呀17 小时前
C++工业级日志项目(七)日志器核心
linux·开发语言·c++·windows
2401_8734794017 小时前
如何用IP离线库批量清洗订单IP,自动标注省市区?
开发语言·网络·python
lcj251117 小时前
vector的基本使用 + 手搓成员变量 size capacity begin end operator[] reserve扩容 拷贝构造 赋值析构
开发语言·c++·笔记·面试
GHL28427109017 小时前
Qt Creator 19.0.0 (Community)下载
开发语言·qt
之歆18 小时前
Day21_电商详情页核心技术实战:从LESS预处理到复杂交互实现
开发语言·前端·javascript·css·交互·less
Mininglamp_271818 小时前
现在入局Agent开发还来得及吗?
java·开发语言
jiayong2318 小时前
MySQL 排序规则冲突问题与 utf8mb4_general_ci 统一方案
android·mysql·ci/cd