非空断言
解决可空类型问题的另一种方法是具有特殊知识,即相关引用不为
null。
要提出这个断言,使用双感叹号 !!,称为非空断言。如果这看起来令人震惊,那是正常的:相信某些东西不能为 null 是大多数与 null 相关的程序故障的根源(其他的故障可能来自于没有意识到 null 可能会发生)。
x!! 的意思是“忘记 x 可能是 null 的事实——我保证它不是 null。” 如果 x 不为 null,则 x!! 产生 x,否则它会抛出异常:
// NonNullAssertions/NonNullAssert.kt
import atomictest.*
fun main() {
var x: String? = "abc"
x!! eq "abc"
x = null
capture {
val s: String = x!!
} eq "NullPointerException"
}
定义 val s: String = x!! 告诉 Kotlin 忽略它认为自己了解的 x 的情况,只是将它赋值给 s,s 是一个非可空引用。幸运的是,有运行时支持,当 x 为 null 时会抛出 NullPointerException。
通常情况下,您不会单独使用 !!,而是与 . 解引用一起使用:
// NonNullAssertions/NonNullAssertCall.kt
import atomictest.eq
fun main() {
val s: String? = "abc"
s!!.length eq 3
}
如果您每行只使用一个非空断言调用,当异常提供行号时,更容易找到故障。
安全调用 ?. 是一个单一的运算符,但非空断言调用由两个运算符组成:非空断言(!!)和解引用(.)。如您在 NonNullAssert.kt 中看到的,您可以单独使用非空断言。
避免使用非空断言,更喜欢使用安全调用或显式检查。非空断言是为了使 Kotlin 与 Java 进行交互而引入的,以及在 Kotlin 不能智能地确保执行必要的检查时的罕见情况。
如果您在代码中经常为同一操作使用非空断言,最好使用一个带有特定断言的单独函数来描述问题。例如,假设您的程序逻辑要求一个特定的键必须存在于 Map 中,并且如果键缺失,则更喜欢抛出异常而不是无声地不做任何操作。与常规方法(方括号)提取值不同,getValue() 在键缺失时会抛出 NoSuchElementException:
// NonNullAssertions/ValueFromMap.kt
import atomictest.*
fun main() {
val map = mapOf(1 to "one")
map[1]!!.toUpperCase() eq "ONE"
map.getValue(1).toUpperCase() eq "ONE"
capture {
map[2]!!.toUpperCase()
} eq "NullPointerException"
capture {
map.getValue(2).toUpperCase()
} eq "NoSuchElementException: " +
"Key 2 is missing in the map."
}
抛出特定的 NoSuchElementException 在出现问题时提供了更有用的详细信息。
- -
最佳实践的代码仅使用安全调用和引发详细异常的特殊函数。只有在绝对必要的情况下才使用非空断言。尽管非空断言是为了支持与 Java 代码的交互而包含的,但与 Java 交互的更好方法,您可以在 附录 B:Java 互操作性 中学习。
练习和答案可以在 www.AtomicKotlin.com 找到。