找回密码
 立即注册
首页 业界区 业界 学习Kotlin语法(四)

学习Kotlin语法(四)

魁睥 2025-6-2 00:36:10
简介

在上一节,我们对Kotlin中函数的相关知识有了大致的了解,本章节我们将去了解一些Kotlin中的作用域函数。
目录


  • let:处理可空对象,链式操作
  • run:对象配置 + 计算返回值
  • with:对非空对象执行多个操作
  • apply:初始化对象配置
  • also:附加操作(如打印日志)
Kotlin 中的作用域函数(Scope Functions)是 let、run、with、apply、also,它们可以简化对对象的操作,使代码更简洁
let:处理可空对象,链式操作

在 Kotlin 中,let 函数通过与 安全调用操作符 ?. 结合使用,优雅地处理可空对象。以下是详细解释:

  • let 处理可空对象的基本原理

    • 语法结构:可空对象?.let{ ... }
    • 关键机制:

      • 如果对象 非空,?. 会触发 let 代码块执行,对象作为参数(默认为 it)传递给 lambda
      • 如果对象 为空,?. 会跳过 let 代码块,整个表达式返回 null ,且 lambda 中的代码 不会执行
        1. fun main() {
        2.     val nullableString: String? = "Hello" // 在类型后面声明 ? 为 可空对象
        3.     val length = nullableString?.let {
        4.         println("执行 let: 对象非空, 内容为 $it") // 非空时才会打印 输出: 执行 let: 对象非空, 内容为 Hello
        5.         it.length // 返回长度 (最后表达式的结果)
        6.     }
        7.     println("字符串长度: $length") // 输出: 字符串长度: 5
        8.     // 对象为空的时候
        9.     val nullString: String? = null
        10.     val nullResult = nullString?.let {
        11.         println("因为对象为空,这里不会执行") // 被跳过
        12.         it.length
        13.     }
        14.     println("空对象的结果是: $nullResult") // 输出: 空对象的结果是: null
        15. }
        复制代码


  • 如果省略安全调用符 ?. (错误示范)
    直接调用 let (不加 ?.) 可能会导致空指针异常(NPE)
    1. fun main() {
    2.     val str: String? = null
    3.     str.let { // 编译警告:此处可能抛出 NPE!
    4.         println(it!!.length) // 如果 str 为 null,
    5.     }
    6. }
    7. /**
    8. * Exception in thread "main" java.lang.NullPointerException
    9. *         at MainKt.main(main.kt:4)
    10. *         at MainKt.main(main.kt)
    11. * */
    复制代码
    所以必须使用 ?.let 处理可空对象
  • 链式操作与默认值处理
    结合 Elvis 操作符 ?:,可以为 let 的返回结果提供默认值:
    1. fun main() {
    2.     val input: String? = null
    3.     val processed = input?.let {
    4.         it.uppercase() // 非空时转换成大写
    5.     } ?: "DEFAULT" // 如何 input 为 null 返回 'DEFAULT'
    6.     println(processed) // 输出: DEFAULT
    7. }
    复制代码
  • 经典使用场景

    • 避免空检查嵌套
      1. data class User(val name: String = "")
      2. fun main() {
      3.     val user = User("NPC")
      4.     // 传统空检查(繁琐)
      5.     if (user != null) {
      6.         if (user.name != null) {
      7.             println(user.name.length) // 输出: 3
      8.         }
      9.     }
      10.     // 使用 ?.let(简洁)
      11.     user?.name?.let { println(it.length) } // 输出: 3
      12. }
      复制代码
    • 数据转换
      1. fun main() {
      2.     val number: Int? = "123".toIntOrNull()
      3.     println(number) // 输出: 123
      4.     val squared = number?.let { it * it } // 非空时计算平方,否则返回 null
      5.     println(squared) // 输出: 15129
      6. }
      复制代码
    • 副作用操作(如打印日志)
      1. fun main() {
      2.     val data = "Hello Kotlin"
      3.     data?.let {
      4.         println("处理数据: $it")
      5.     }
      6. }
      复制代码

run:对象配置 + 计算返回值

在 Kotlin 中,run 是一个灵活的作用域函数,它有两种形式:扩展函数非扩展函数。它的核心用途是在对象的上下文中执行代码块,并返回 lambda 表达式的结果。以下是 run 的详细讲解

  • run 的两种形式

    • 形式1: 拓展函数(对象引用)
      1. 对象.run {
      2.     // 代码块:通过 this 访问对象
      3.     // 最后一行作为返回值
      4. }
      复制代码

      • 上下文: 代码内使用 this 引用对象(可省略)
      • 返回值: lambda 的最后一行结果

    • 形式2: 非拓展函数(独立作用域)
      1. run {
      2.     // 独立代码块(无需对象)
      3.     // 最后一行作为返回值
      4. }
      复制代码

      • 用途: 创建一个临时作用域,避免变量污染外部环境


  • 示例代码

    • 扩展函数(操作对象并返回结果)
      在 Car 对象上下文中配置属性,并返回一个描述状态的字符串。
      1. data class Car(var speed: Int = 0, var isEngineOn: Boolean = false)
      2. fun main() {
      3.     val carStatus = Car().run {
      4.         this.speed = 100 // 直接访问属性(this 可省略)
      5.         isEngineOn = true // 修改对象状态
      6.         "车速: $speed km/h,引擎状态: ${if (isEngineOn) "开启" else "关闭"}" // 返回字符串
      7.     }
      8.     println(carStatus) // 输出: 车速: 100 km/h,引擎状态: 开启
      9. }
      复制代码
    • 处理可空对象(结合空安全调用?.run)
      仅在对象非空时执行代码块,避免空指针异常。
      1. fun printLengthIfNotNull(input: String?) {
      2.     input?.run {
      3.         println("字符串内容: $this, 长度: $length") // this 指代 input 对象
      4.     } ?: println("输入为空")
      5. }
      6. fun main() {
      7.     printLengthIfNotNull("Hello, Kotlin") // 输出: 字符串内容: Hello, Kotlin, 长度: 13
      8.     printLengthIfNotNull(null) // 输出: 输入为空
      9. }
      复制代码
    • 非扩展形式(独立作用域)
      封装临时计算逻辑,避免变量 a 和 b 泄漏到外部作用域。
      1. fun main() {
      2.     val result = run {
      3.         val a = 10
      4.         val b = 20
      5.         a + b // 返回计算结果
      6.     }
      7.     println("计算结果:$result") // 输出:计算结果:30
      8. }
      复制代码

  • 高级用法

    • 链式调用多个 run
      1. fun main() {
      2.     val message = "Kotlin"
      3.         .run { uppercase() } // 转换为大写
      4.         .run { "Message: $this" } // 添加前缀
      5.     println(message) // 输出: Message: KOTLIN
      6. }
      复制代码
    • 与 apply 结合使用
      1. data class Config(var host: String = "", var port: Int = 0)
      2. fun main() {
      3.     val config = Config().apply {
      4.         host = "127.0.0.1" // 初始化配置
      5.         port = 8080
      6.     }.run {
      7.         "服务器地址: $host:$port" // 转换为连接字符串
      8.     }
      9.     println(config) // 输出: 服务器地址: 127.0.0.1:8080
      10. }
      复制代码

apply:初始化对象配置

在 Kotlin 中,apply 是一个常用的作用域函数,专门用于对象的初始化或配置。它通过简洁的语法让你在一个代码块中完成对对象属性的设置,并最终返回对象本身,以下是 apply 的详细讲解

  • 基本语法
    1. val 对象 = 原始对象.apply {
    2.     // 在此配置对象的属性或调用方法
    3.     this.property = value  // this 可省略
    4.     method()
    5.     // ...
    6. }
    7. // apply 返回原始对象,可以继续操作
    复制代码
  • 示例代码

    • 初始化对象属性
      1. data class Person(var name: String = "", var age: Int = 0)
      2. fun main() {
      3.     val person = Person().apply {
      4.         name = "Alice" // 等价于 this.name = "Alice"
      5.         age = 30 // 直接访问属性, this 可省略
      6.     }
      7.     println(person) // 输出: Person(name=Alice, age=30)
      8. }
      复制代码
    • 链式配置多个属性
      1. class Car {
      2.     var brand: String = ""
      3.     var speed: Int = 0
      4.     fun start() { println("$brand 启动,速度: $speed km/h") }
      5. }
      6. fun main() {
      7.     var car = Car()
      8.         .apply { brand = "XiaoMi SU7" }
      9.         .apply { speed = 200 }
      10.         .apply { start() } // 输出: XiaoMi SU7 启动,速度: 200 km/h
      11. }
      复制代码
    • 替代 Builder 模式
      1. // 传统 Builder 模式 vs apply 简化
      2. class Dialog {
      3.     var title: String = ""
      4.     var message: String = ""
      5.     fun show() { println("显示对话框:$title - $message") }
      6. }
      7. fun main() {
      8.     // 传统方式
      9.     val dialog1 = Dialog()
      10.     dialog1.title = "提示"
      11.     dialog1.message = "欢迎使用 Kotlin"
      12.     dialog1.show()
      13.     // 使用 apply(更简洁)
      14.     val dialog2 = Dialog().apply {
      15.         title = "提示"
      16.         message = "欢迎使用 Kotlin"
      17.         show() // 直接调用方法
      18.     }
      19. }
      复制代码
    • 处理可空对象
      1. fun configureNullableObject() {
      2.     val nullableConfig: String? = null
      3.     nullableConfig?.apply {
      4.         println("配置非空对象:$this") // 此处不会执行
      5.     } ?: println("对象为空") // 输出:对象为空
      6. }
      7. fun main() {
      8.     configureNullableObject()
      9. }
      复制代码

  • 常见误区

    • 错误:在 apply 中返回其他值
      1. data class Person (var name: String = "",var age: Int = 0)
      2. fun main() {
      3.     val person = Person().apply {
      4.         name = "Bob"
      5.         "这是一个错误示例" // 这行代码无效
      6.     }
      7.     println(person)
      8. }
      复制代码
    • 正确的做法是: 若需要返回计算结果,应使用 run:
      1. data class Person (var name: String = "",var age: Int = 0)
      2. fun main() {
      3.     val person = Person().run {
      4.         name = "Bob"
      5.         "姓名: $name"
      6.     }
      7.     println(person) // 输出: 姓名: Bob
      8. }
      复制代码

  • 高级用法

    • 链式调用多个作用域函数
      1. data class File(var absolutePath: String = "") {
      2.     fun readText() {
      3.         // ... 读取文件操作
      4.         println("读取文件操作")
      5.     }
      6. }
      7. fun createNewFile() {
      8.     // ... 创建文件操作
      9.     println("创建文件操作")
      10. }
      11. fun main() {
      12.     File("data.txt")
      13.         .apply { createNewFile() }
      14.         .also { println("文件路径: ${it.absolutePath}") }
      15.         .readText()
      16. }
      复制代码
    • 结合 takeIf 过滤条件
      1. data class Person(var name: String = "", var age: Int = 0)
      2. fun main() {
      3.     val validPerson = Person().apply {
      4.         name = "Charlie"
      5.         age = 25
      6.     }.takeIf { it.age >= 18 } // 只保留成年人
      7.     println(validPerson) // 输出:Person(name=Charlie, age=25)
      8. }
      复制代码

with:对非空对象执行多个操作

在 Kotlin 中,with 是一个作用域函数,用于对已有对象执行多个操作,它通过将对象作为上下文(this)传递到代码块中,让代码更集中、更易读。以下是 with 的详细讲解

  • 基本语法
    1. val 结果 = with(对象) {
    2.     // 在此直接访问对象的属性和方法(this 可省略)
    3.     操作1
    4.     操作2
    5.     ...
    6.     最后一行作为返回值
    7. }
    复制代码
  • 示例代码

    • 批量操作对象属性
      1. data class User(var name: String = "", var age: Int = 0)
      2. fun main() {
      3.     val user = User("Alice", 25)
      4.     val info = with(user) {
      5.         name = "Bob"       // 等价于 this.name = "Bob"
      6.         age += 5           // 修改属性
      7.         "更新后:$name, $age 岁" // 返回字符串
      8.     }
      9.     println(info)  // 输出:更新后:Bob, 30 岁
      10.     println(user)  // 输出:User(name=Bob, age=30)
      11. }
      复制代码
    • 执行计算并返回结果
      1. class Rectangle(val width: Int, val height: Int) {
      2.     fun area() = width * height
      3. }
      4. fun main() {
      5.     val rect = Rectangle(10, 20)
      6.     val result = with(rect) {
      7.         val perimeter = 2 * (width + height) // 访问属性
      8.         "面积: ${area()}, 周长: $perimeter"   // 调用方法,返回字符串
      9.     }
      10.     println(result) // 输出:面积: 200, 周长: 60
      11. }
      复制代码
    • 处理集合操作
      1. fun main() {
      2.     val numbers = listOf(1, 2, 3, 4, 5)
      3.     val summary = with(numbers) {
      4.         val sum = sum()
      5.         val avg = average()
      6.         "总和: $sum, 平均值: $avg" // 返回统计结果
      7.     }
      8.     println(summary) // 输出:总和: 15, 平均值: 3.0
      9. }
      复制代码

  • 常见误区

    • 错误:对可空对象直接使用 with
      1. data class Person(var name: String = "", var age: Int = 0)
      2. fun main() {
      3.     val person: Person? = null
      4.     with(person) {
      5.         println(name) // 编译报错 Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Person?
      6.     }
      7. }
      复制代码
    • 正确做法
      1. data class Person(var name: String = "", var age: Int = 0)
      2. fun main() {
      3.     val user: Person? = null
      4.     with(user ?: return) { // 如果 user 为 null,提前退出
      5.         println(name)
      6.     }
      7. }
      复制代码

  • 经典应用场景

    • 集中配置对象属性
      1. data class Person(var name: String = "", var age: Int = 0) {
      2.     fun sout() {
      3.         println("Name: $name, Age: $age")
      4.     }
      5. }
      6. fun main() {
      7.     val person = Person()
      8.     with(person) {
      9.         name = "NPC"
      10.         age = 24
      11.         sout() // 输出: Name: NPC, Age: 24
      12.     }
      13. }
      复制代码
    • 数据转换与计算
      1. fun main() {
      2.     val list = listOf(1, 2, 3)
      3.     val squaredSum = with(list) {
      4.         map { it * it }.sum()
      5.     }
      6.     println(squaredSum) // 输出: 14
      7. }
      复制代码
    • 简化多步操作
      1. data class File(var fileName: String ="") {
      2.     fun createNewFile() {
      3.         println("创建文件 $fileName")
      4.     }
      5.     fun exists(): Boolean {
      6.         println("判断 $fileName 文件是否存在")
      7.         return false
      8.     }
      9.     fun readText(): String {
      10.         return "读取文件 $fileName"
      11.     }
      12. }
      13. fun main() {
      14.     val file = File("data.txt")
      15.     val content = with(file) {
      16.         if (!exists()) createNewFile()
      17.         readText().uppercase()
      18.     }
      19.     println(content) // 输出: 读取文件 DATA.TXT
      20. }
      复制代码

also:附加操作(如打印日志)

在 Kotlin 中,also 是一个作用域函数,专注于执行附加操作(如日志记录、验证、调试),同时保留对象本身,并支持链式调用。它不会修改对象,但允许在对象操作流程中插入“副作用”。以下是 also 的详细讲解

  • 基本语法
    1. val 对象 = 原始对象.also {
    2.     // 通过 it 访问对象,执行附加操作
    3.     // 返回原始对象(无论代码块内做了什么)
    4. }
    复制代码
  • 示例代码

    • 打印日志(调试中间状态)
      1. data class User(var name: String, var age: Int)
      2. fun main() {
      3.     val user = User("Alice", 25)
      4.         .also { println("初始化后:$it") } // 输出:初始化后:User(name=Alice, age=25)
      5.         .apply { age += 5 }
      6.         .also { println("修改年龄后:$it") } // 输出:修改年龄后:User(name=Alice, age=30)
      7.     println("最终结果:$user") // 输出:最终结果:User(name=Alice, age=30)
      8. }
      复制代码
    • 数据验证
      1. fun processOrder(order: Order?) {
      2.     order?.also {
      3.         require(it.amount > 0) { "订单金额必须大于 0" }
      4.         requireNotNull(it.customer) { "订单必须有客户信息" }
      5.     }?.also {
      6.         println("开始处理订单:${it.id}") // 验证通过后执行
      7.     }
      8. }
      9. data class Order(val id: String, var amount: Double, var customer: String?)
      10. fun main() {
      11.     val validOrder = Order("123", 100.0, "Alice")
      12.     processOrder(validOrder) // 输出:开始处理订单:123
      13.     val invalidOrder = Order("456", -50.0, null)
      14.     processOrder(invalidOrder) // 抛出 IllegalArgumentException
      15. }
      复制代码
    • 链式调用中插入操作
      1. fun main() {
      2.     val list = mutableListOf(1, 2, 3)
      3.         .also { it.add(4) } // 添加元素
      4.         .also { it.remove(0) } // 删除第一个元素(实际无 0,此操作为演示)
      5.         .also { println("当前列表:$it") } // 输出:当前列表:[2, 3, 4]
      6.     println(list) // 输出:[2, 3, 4]
      7. }
      复制代码

  • 常见误区

    • 错误: 在 also 中尝试返回其他值
      1. data class User(var name: String, var age: Int)
      2. fun main() {
      3.     // 错误!also 永远返回原始对象,忽略代码块内的返回值
      4.     val user = User("Alice", 25).also {
      5.         "无效的返回值" // 这行代码无意义
      6.     }
      7.     println(user) // 输出:User(name=Alice, age=25)
      8. }
      复制代码
    • 正确做法: 若需返回计算结果,应使用 let 或 run
      1. data class User(var name: String, var age: Int)
      2. fun main() {
      3.     val info = User("Alice", 25).let {
      4.         "${it.name} 的年龄是 ${it.age}" // 返回字符串
      5.     }
      6.     println(info) // 输出:Alice 的年龄是 25
      7. }
      复制代码

  • 高级用法

    • 结合 takeIf 进行条件过滤
      1. data class User(var name: String, var age: Int)
      2. fun createUser(name: String?, age: Int): User? {
      3.     return name?.let {
      4.         User(it, age)
      5.     }?.takeIf { it.age >= 18 } // 只保留成年人
      6.         ?.also { println("用户创建成功:$it") } // 记录日志
      7. }
      8. fun main() {
      9.     val user = createUser("Bob", 20) // 输出:用户创建成功:User(name=Bob, age=20)
      10.     println(user) // 输出:User(name=Bob, age=20)
      11. }
      复制代码
    • 再集合操作中跟踪流程
      1. fun main() {
      2.     val numbers = (1..10)
      3.         .filter { it % 2 == 0 }
      4.         .also { println("过滤后的偶数:$it") } // 输出:过滤后的偶数:[2, 4, 6, 8, 10]
      5.         .map { it * it }
      6.         .also { println("平方结果:$it") } // 输出:平方结果:[4, 16, 36, 64, 100]
      7. }
      复制代码

核心对比表

函数上下文对象引用返回值典型场景空安全支持letitLambda结果可空对象处理、数据转换需配合?.(object?.let)runthisLambda结果对象配置 + 返回结果计算、独立作用域需配合?.(object?.run)applythis对象本身对象初始化(链式配置属性)需配合?.(object?.apply)withthisLambda结果对已有非空对象批量操作需自行处理空安全alsoit对象本身附加操作(日志、验证)、链式调用中的中间操作需配合?.(object?.also)

  • 上下文对象引用
    1. data class Car(var speed: Int = 0) {
    2.     fun accelerate() { speed += 10 }
    3. }
    4. fun main() {
    5.     val car = Car().apply {
    6.         speed = 100  // 直接访问属性(this 可省略)
    7.         accelerate() // 直接调用方法
    8.     }
    9.     println(car) // 输出:Car(speed=110)
    10. }
    复制代码
  • it (显示引用):let 、also
    1. fun main() {
    2.     val list = mutableListOf(1, 2, 3)
    3.         .also { it.add(4) } // 显式用 it 操作
    4.         .let { it.joinToString("-") } // 转换为字符串
    5.     println(list) // 输出:1-2-3-4
    6. }
    复制代码
  • 返回对象本身:apply 、also
    1. data class Button(var text: String = "", var textSize: Float = 0.0f)
    2. fun main() {
    3.     // apply 返回对象本身,适合链式配置
    4.     val button = Button().apply {
    5.         text = "Submit"
    6.         textSize = 16f
    7.     }
    8.     // also 返回对象本身,适合插入附加操作
    9.     button.also { println("按钮已配置:$it") } // 输出: 按钮已配置:Button(text=Submit, textSize=16.0)
    10. }
    复制代码
  • 返回 Lambda 结果:let 、run 、also
    1. data class Car(var speed: Int = 0) {
    2.     fun accelerate() { speed += 10 }
    3. }
    4. fun main() {
    5.     // let 返回转换后的数据
    6.     val length = "Kotlin".let { it.length }
    7.     println(length) // 输出: 6
    8.     // run 返回计算结果
    9.     val area = Car(200).run { speed * 2 }
    10.     println(area) // 输出: 400
    11.     // with 返回处理后的结果
    12.     val info = with(Car()) {
    13.         speed = 100
    14.         "车速:$speed km/h"
    15.     }
    16.    
    17.     println(info) // 输出: 车速: 100 km/h
    18. }
    复制代码
  • 需配合 ?. : let 、run 、 apply 、 also
    1. fun main() {
    2.     val nullableString: String? = null
    3.     nullableString?.let {
    4.         println(it.length) // 非空时执行
    5.     } ?: println("字符串为空")
    6.     nullableString?.apply {
    7.         println(length) // 非空时执行
    8.     }
    9. }
    复制代码
  • 需自行处理: with
    1. data class User(var name: String = "", var age: Int = 0)
    2. fun main() {
    3.     val user: User? = User()
    4.     user?.let {
    5.         with(it) {  // 确保非空后使用 with
    6.             name = "Alice"
    7.             age = 30
    8.         }
    9.     }
    10.     println(user) // 输出: User(name=Alice, age=30)
    11. }
    复制代码
选择指南


  • 是否需要返回对象本身?

    • 是 → apply 或 also(根据是否需要显式 it)。
    • 否 → let、run、with(根据是否需要隐式 this)。

  • 是否需要处理可空对象?

    • 是 → ?.let、?.run、?.apply、?.also。
    • 否 → with 或直接使用其他函数。

  • 是否需要链式调用中的中间操作?

    • 是 → also(如记录日志)。
    • 否 → 根据返回值需求选择。

混合使用示例
  1. // 链式调用:初始化、验证、转换
  2. data class Product(var name: String = "", var price: Double = 0.0)
  3. fun main() {
  4.     val productInfo = Product()
  5.         .apply {
  6.             name = "手机"
  7.             price = 2999.0
  8.         }
  9.         .also {
  10.             require(it.price > 0) { "价格必须大于 0" }
  11.             println("商品已初始化:$it")
  12.         }
  13.         .let {
  14.             "${it.name} 价格:${it.price} 元" // 返回字符串
  15.         }
  16.     println(productInfo) // 输出:手机 价格:2999.0 元
  17. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册