最近看到这么一个代码片段,里面用到了好几种 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: 当expression为null时返回的默认值。
示例
- 基本用法:
val name: String? = null
val displayName = name ?: "Default Name"
println(displayName) // 输出 "Default Name"
在这个例子中,由于 name 是 null,所以 displayName 被赋值为 "Default Name"。
- 与函数返回值一起使用:
fun getName(): String? {
return null
}
val name = getName() ?: "Unknown"
println(name) // 输出 "Unknown"
如果 getName() 返回 null,则 name 被赋值为 "Unknown"。
- 链式使用:
val name: String? = null
val fallbackName: String? = null
val finalName = name ?: fallbackName ?: "Default Name"
println(finalName) // 输出 "Default Name"
如果 name 和 fallbackName 都是 null,则 finalName 被赋值为 "Default Name"。
使用场景
- 默认值处理:
在处理可能为 null 的值时,?: 操作符可以提供一个默认值,避免 NullPointerException。
val length = text?.length ?: 0
如果 text 为 null,则 length 被赋值为 0。
- 简化代码:
使用 ?: 可以减少对 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返回true,takeIf返回obj本身。 - 如果
predicate返回false,takeIf返回null。
示例
- 基本用法:
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。
- 与
?.操作符结合使用:
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为空字符串。
- 过滤列表:
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.mapNotNull { it.takeIf { it % 2 == 0 } }
println(evenNumbers) // 输出 [2, 4]
在这个示例中,takeIf 与 mapNotNull 结合使用,过滤出所有偶数。
与 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
run 和 let 都可以用于在对象的上下文中执行代码块,但有一些区别:
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