安全调用和 Elvis 运算符
Kotlin 提供了方便的操作来处理可空性。
可空类型带来了许多限制。您不能简单地解引用可空类型的标识符:
// SafeCallsAndElvis/DereferenceNull.kt
fun main() {
val s: String? = null
// 不能编译:
// s.length // [1]
}
取消 [1] 的注释会产生编译时错误:Only safe (?.
) or non-null asserted (!!.
) calls are allowed on a nullable receiver of type String?
.。
安全调用将常规调用中的点(.
)替换为问号和点(?.
),中间没有空格。安全调用以确保不会抛出异常的方式访问可空类型的成员。它们仅在接收方不为 null
时执行操作:
// SafeCallsAndElvis/SafeOperation.kt
package safecalls
import atomictest.*
fun String.echo() {
trace(toUpperCase())
trace(this)
trace(toLowerCase())
}
fun main() {
val s1: String? = "Howdy!"
s1?.echo() // [1]
val s2: String? = null
s2?.echo() // [2]
trace eq """
HOWDY!
Howdy!
howdy!
"""
}
[1] 行调用 echo()
并在 trace
中产生结果,而 [2] 行不执行任何操作,因为接收方 s2
是 null
。
安全调用是一种捕获结果的简洁方式:
// SafeCallsAndElvis/SafeCall.kt
package safecalls
import atomictest.eq
fun checkLength(s: String?, expected: Int?) {
val length1 =
if (s != null) s.length else null // [1]
val length2 = s?.length // [2]
length1 eq expected
length2 eq expected
}
fun main() {
checkLength("abc", 3)
checkLength(null, null)
}
[2] 行实现了与 [1] 行相同的效果。如果接收方不为 null
,它会执行正常的访问(s.length
)。如果接收方为 null
,它不会执行 s.length
调用(这会导致异常),而是对表达式产生 null
。
如果您需要的不仅仅是 ?.
产生的 null
,Elvis 运算符 提供了一种替代方法。该运算符是一个问号后跟一个冒号(?:
),中间没有空格。它是以音乐家 Elvis Presley 的表情符号命名的,也是 “else-if” 这个词的一种变种(听起来有点像 “Elvis”)。
许多编程语言都提供了与 Kotlin 的 Elvis 运算符执行相同操作的 null 合并运算符。
如果 ?:
左侧的表达式不为 null
,则该表达式成为结果。如果左侧表达式 是 null
,则 ?:
右侧的表达式成为结果:
// SafeCallsAndElvis/ElvisOperator.kt
import atomictest.eq
fun main() {
val s1: String? = "abc"
(s1 ?: "---") eq "abc"
val s2: String? = null
(s2 ?: "---") eq "---"
}
s1
不为 null
,因此 Elvis 运算符将 "abc"
作为结果。由于 s2
是 null
,Elvis 运算符产生备用结果 "---"
。
Elvis 运算符通常在安全调用之后使用,以生成有意义的值,而不是默认的 null
,就像 [2] 中看到的那样:
// SafeCallsAndElvis/ElvisCall.kt
package safecalls
import atomictest.eq
fun checkLength(s: String?, expected: Int) {
val length1 =
if (s != null) s.length else 0 // [1]
val length2 = s?.length ?: 0 // [2]
length1 eq expected
length2 eq expected
}
fun main() {
checkLength("abc", 3)
checkLength(null, 0)
}
这个 checkLength()
函数与上面的 SafeCall.kt
中的函数非常相似。expected
参数的类型现在是非可空的。[1] 和 [2] 在产生 null
代替的位置上产生了零。
当您使用安全调用来链接访问多个成员时,如果任何中间表达式为 null
,结果将为 null
:
// SafeCallsAndElvis/ChainedCalls.kt
package safecalls
import atomictest.eq
class Person(
val name: String,
var friend: Person? = null
)
fun main() {
val alice = Person("Alice")
alice.friend?.friend?.name eq null // [1]
val bob = Person("Bob")
val charlie = Person("Charlie", bob)
bob.friend = charlie
bob.friend?.friend?.name eq "Bob" // [2]
(alice.friend?.friend?.name
?: "Unknown") eq "Unknown" // [3]
}
当您使用安全调用来链式访问多个成员时,如果任何中间表达式为 null
,结果将为 null
。
- [1] 属性
alice.friend
为null
,因此其他调用的返回值都是null
。 - [2] 所有中间调用都产生有意义的值。
- [3] 安全调用链之后的 Elvis 运算符提供了一个备用值,如果任何中间元素为
null
。
练习和答案可以在 www.AtomicKotlin.com 找到。