"?"、"?."、"?:"、"takeIf"、"run" 等语法讲解

2,448 阅读4分钟

最近看到这么一个代码片段,里面用到了好几种 kotlin 特有的语法,让代码更简洁,觉得挺好的。 就以这个代码片段用到这些语法做些讲解。

private lateinit var selectionArgs: Array<String>
private var searchString: String? = null
private var selectionClause: String? = null

selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {  
    selectionClause = "${UserDictionary.Words.WORD} = ?"  
    arrayOf(it)  
} ?: run {  
    selectionClause = null  
    emptyArray<String>()  
}

? 可空类型

在 Kotlin 中,类型默认是不可空的。如果要声明一个可空类型,需要在类型后面加上 ?

val name: String = "Kotlin" // 不可空类型
val nullableName: String? = "Kotlin" // 可空类型
val anotherNullableName: String? = null // 可空类型,可以为 null

?. 安全调用操作符

安全调用操作符用于调用一个可能为 null 的对象的方法或访问其属性。如果对象为 null,调用结果为 null,否则正常调用。

val length: Int? = nullableName?.length // 如果 nullableName 为 null,length 也是 null

?: Elvis 操作符

Kotlin 的 ?: 语法被称为 "Elvis 操作符"。它用于提供一个默认值,当表达式的结果为 null 时返回这个默认值。具体来说,a ?: b 表示如果 a 为非 null,则返回 a;否则,返回 b

语法和用法

val result = expression ?: defaultValue
  • expression: 可能为 null 的表达式。
  • defaultValue: 当 expressionnull 时返回的默认值。

示例

  1. 基本用法
val name: String? = null
val displayName = name ?: "Default Name"
println(displayName)  // 输出 "Default Name"

在这个例子中,由于 namenull,所以 displayName 被赋值为 "Default Name"

  1. 与函数返回值一起使用
fun getName(): String? {
    return null
}

val name = getName() ?: "Unknown"
println(name)  // 输出 "Unknown"

如果 getName() 返回 null,则 name 被赋值为 "Unknown"

  1. 链式使用
val name: String? = null
val fallbackName: String? = null
val finalName = name ?: fallbackName ?: "Default Name"
println(finalName)  // 输出 "Default Name"

如果 namefallbackName 都是 null,则 finalName 被赋值为 "Default Name"

使用场景

  1. 默认值处理

在处理可能为 null 的值时,?: 操作符可以提供一个默认值,避免 NullPointerException

val length = text?.length ?: 0

如果 textnull,则 length 被赋值为 0

  1. 简化代码

使用 ?: 可以减少对 null 值的显式检查,使代码更简洁、更具可读性。

与 Java 的对比

在 Java 中,实现相同的逻辑通常需要使用三元操作符和显式的 null 检查:

String name = getName();
String displayName = (name != null) ? name : "Default Name";

而在 Kotlin 中,可以通过 ?: 操作符简化为:

val displayName = getName() ?: "Default Name"

Kotlin 的 ?: 操作符提供了一种简洁且安全的方式处理 null 值,使代码更加简洁、可读。

takeIf 函数

Kotlin 的 takeIf 是一个标准库函数,用于根据给定的谓词(条件)返回对象本身或 null。如果对象满足条件,takeIf 返回对象本身;如果对象不满足条件,takeIf 返回 null

语法

val result = obj.takeIf { predicate }
  • obj:任何对象。
  • predicate:一个返回布尔值的 lambda 表达式或函数。

工作原理

  • 如果 predicate 返回 truetakeIf 返回 obj 本身。
  • 如果 predicate 返回 falsetakeIf 返回 null

示例

  1. 基本用法
val number = 42
val result = number.takeIf { it > 10 }
println(result) // 输出 42

val result2 = number.takeIf { it < 10 }
println(result2) // 输出 null

在这个示例中:

  • number.takeIf { it > 10 } 返回 number(即 42),因为 42 大于 10。
  • number.takeIf { it < 10 } 返回 null,因为 42 不小于 10。
  1. ?. 操作符结合使用
val text: String? = "Hello, Kotlin"
val result = text?.takeIf { it.isNotEmpty() }
println(result) // 输出 "Hello, Kotlin"

val emptyText: String? = ""
val result2 = emptyText?.takeIf { it.isNotEmpty() }
println(result2) // 输出 null

在这个示例中:

  • text?.takeIf { it.isNotEmpty() } 返回 text,因为 text 非空。
  • emptyText?.takeIf { it.isNotEmpty() } 返回 null,因为 emptyText 为空字符串。
  1. 过滤列表
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.mapNotNull { it.takeIf { it % 2 == 0 } }
println(evenNumbers) // 输出 [2, 4]

在这个示例中,takeIfmapNotNull 结合使用,过滤出所有偶数。

let 结合

takeIf 通常与 let 结合使用,以便在对象满足特定条件时执行某些操作。

val input: String? = "Kotlin"
input?.takeIf { it.isNotEmpty() }?.let { println("Input is: $it") }
// 输出 "Input is: Kotlin"

在这个示例中:

  • 如果 input 非空且不为空字符串,则打印 input
  • 否则,不执行任何操作。

与 Java 的对比

在 Java 中,实现类似功能需要更多的代码和显式的条件检查:

Integer number = 42;
Integer result = (number > 10) ? number : null;
System.out.println(result); // 输出 42

Integer result2 = (number < 10) ? number : null;
System.out.println(result2); // 输出 null

在 Kotlin 中,takeIf 提供了一种更简洁和函数式的方式来实现相同的逻辑。

总结

Kotlin 的 takeIf 是一个强大且灵活的函数,用于基于条件返回对象本身或 null。它有助于减少条件检查的样板代码,并且与 Kotlin 的其他特性(如 ?.let)结合使用时,可以编写出简洁且可读性高的代码。

run 内置函数

在 Kotlin 中,run 是一个内联扩展函数,通常用于执行一个代码块,并返回其最后一个表达式的结果。它可以在对象上调用,也可以作为一个普通的代码块。

在 Kotlin 中,run 是一个内置的标准库函数,提供了一种简洁的方式来在某个对象的上下文中执行代码块,并返回代码块的结果。run 有两个主要的使用形式:扩展函数形式无接收者形式。下面详细讲解这两种形式的语法和用法。

扩展函数形式

扩展函数形式的 run 可以在任何对象上调用,将该对象作为接收者(即 this),并执行代码块。代码块的结果作为 run 函数的返回值。

语法

val result = obj.run {
    // 在这里可以访问 obj 作为 this
    // 最后一行是返回值
}

示例

data class Person(var name: String, var age: Int)

val person = Person("Alice", 29)

val result = person.run {
    name = "Bob"
    age += 1
    "Updated person: $name, $age" // 这将是 run 的返回值
}

println(result) // 输出 "Updated person: Bob, 30"
println(person) // 输出 "Person(name=Bob, age=30)"

在这个例子中,run 的代码块中可以直接访问和修改 person 对象的属性,最后一行 "Updated person: $name, $age" 成为 run 的返回值。

无接收者形式

无接收者形式的 run 不依赖于特定的对象,而是执行一个代码块并返回结果。它通常用于在一个独立的代码块中执行一些操作,并返回一个值。

语法

val result = run {
    // 这里没有接收者对象
    // 最后一行是返回值
}

示例

val result = run {
    val x = 10
    val y = 20
    x + y // 这将是 run 的返回值
}

println(result) // 输出 30

在这个例子中,run 代码块中的最后一行 x + y 成为 run 的返回值。

使用场景

1. 初始化对象

run 常用于初始化对象并返回对象本身或一些派生值。

val person = Person("Alice", 29).run {
    name = "Bob"
    age += 1
    this // 返回修改后的对象
}

println(person) // 输出 "Person(name=Bob, age=30)"

2. 表达式形式的代码块

run 可以用于将一系列表达式合并为一个表达式,尤其在需要返回计算结果的情况下。

val result = run {
    val x = 10
    val y = 20
    x + y // 返回计算结果
}

println(result) // 输出 30

3. 非空检查

run 可以与安全调用操作符 ?. 结合使用,简化非空检查后的逻辑处理。

val nullableString: String? = "Hello"
val length = nullableString?.run {
    this.length
}

println(length) // 输出 5

对比 let

runlet 都可以用于在对象的上下文中执行代码块,但有一些区别:

  • run 使用对象作为接收者(this),而 let 使用对象作为参数(it)。
  • run 更适合在对象上下文中执行代码,而 let 更适合在需要传递对象给代码块时使用。

示例对比

// run 示例
val person = Person("Alice", 29).run {
    name = "Bob"
    age += 1
    this // 返回修改后的对象
}

// let 示例
val person = Person("Alice", 29).let {
    it.name = "Bob"
    it.age += 1
    it // 返回修改后的对象
}

总结

Kotlin 的 run 函数是一种强大而灵活的工具,用于在对象的上下文中执行代码块并返回结果。通过了解和使用 run,可以编写出更加简洁、可读性高的代码,并有效地处理对象初始化、表达式计算、非空检查等常见编程任务。

延伸

!! 非空断言操作符

非空断言操作符用于明确表示一个可空类型的值不会为 null。如果断言失败,会抛出 NullPointerException

val length: Int = nullableName!!.length // 如果 nullableName 为 null,会抛出 NullPointerException

as?安全类型转换

安全类型转换操作符用于尝试将一个值转换为指定类型,如果转换失败,则返回 null。

val obj: Any = "Kotlin"
val str: String? = obj as? String // 如果 obj 不是 String 类型,str 为 null