首先,您应该阅读有关Kotlin中Null Safety的所有内容,该内容全面涵盖了各种情况。
在Kotlin中,您不能访问可空值而不确定它是否为null
(检查条件是否为null),或者使用!!
sure operator断言它肯定不是null
,使用?.
Safe Call访问它,或者最后使用?:
Elvis Operator给可能为null
的东西一个默认值。
对于您问题中的第一个案例,根据代码意图,您有几个选项可供选择,所有选项都是惯用语,但结果不同:
val something: Xyz? = createPossiblyNullXyz()
val result1 = something!!.foo()
val result2 = something?.foo()
val result3 = something?.foo() ?: differentValue
val result4 = if (something != null) {
something.foo()
} else {
...
differentValue
}
if (something != null) {
something.foo()
} else {
someOtherAction()
}
针对“为什么进行空值检查后能够正常工作”,请阅读下面关于
智能转换的背景信息。
针对您在问题中提到的第二种情况,如果您作为开发者确定结果永远不会为
null
,请使用
!!
安全操作符作为断言。
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
在另一种情况下,当地图可能返回空值但您可以提供默认值时,
Map
本身具有
getOrElse
方法:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid
背景信息:
注意:在下面的示例中,我使用显式类型来明确行为。对于局部变量和私有成员,通常可以省略类型推断。
更多关于!!
操作符
!!
操作符断言该值不是null
,否则会抛出NPE异常。这应该在开发人员保证该值永远不会为null
的情况下使用。可以将其视为一个智能转换后的断言。
val possibleXyz: Xyz? = ...
val surelyXyz: Xyz = possibleXyz!!
possibleXyz!!.foo()
阅读更多:!! 确定操作符
更多关于null
检查和智能转换的内容
如果您使用null
检查来保护对可空类型的访问,在语句体中,编译器会将该值智能转换为非空类型。虽然在某些复杂的情况下可能无法实现智能转换,但对于常见情况而言,这种方法是有效的。
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
possiblyXyz.foo()
val surelyXyz: Xyz = possibleXyz
}
或者,如果你对一个非空类型进行了is
检查:
if (possibleXyz is Xyz) {
possiblyXyz.foo()
}
同样适用于安全转换的 'when' 表达式:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
when (possibleXyz) {
is Xyz -> possibleXyz.foo()
is Alpha -> possibleXyz.dominate()
is Fish -> possibleXyz.swim()
}
有些情况下,不允许使用null
检查来智能转换变量以后使用。上面的示例使用了一个本地变量,在应用程序的流程中无论是val
还是var
,这个变量都没有机会变为null
。但是,在其他情况下,编译器无法保证流分析,这将是一个错误:
var nullableInt: Int? = ...
public fun foo() {
if (nullableInt != null) {
val nonNullableInt: Int = nullableInt
}
}
The lifecycle of the variable
nullableInt
is not completely visible and may be assigned from other threads, the
null
check cannot be
smart cast into a non-nullable value. See the "Safe Calls" topic below for a workaround.
Another case that cannot be trusted by a
smart cast to not mutate is a
val
property on an object that has a custom getter. In this case, the compiler has no visibility into what mutates the value and therefore you will get an error message.
class MyThing {
val possibleXyz: Xyz?
get() { ... }
}
val thing = MyThing()
if (thing.possibleXyz != null) {
thing.possiblyXyz.foo()
}
阅读更多:在条件中检查 null
更多关于?.
安全调用运算符
安全调用运算符在左侧的值为null时返回null,否则继续评估右侧的表达式。
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
val answer = possibleXyz?.foo()?.goo()?.boo()
另一个例子是当你想要迭代一个列表但仅在不为 null
和不为空时,安全调用运算符会派上用场:
val things: List? = makeMeAListOrDont()
things?.forEach {
}
在上面的一个例子中,我们遇到了这样一种情况:我们进行了一个
if
检查,但是有可能另一个线程改变了这个值,因此没有
智能转换。为了解决这个问题,我们可以改变这个示例,使用安全调用运算符和
let
函数:
var possibleXyz: Xyz? = 1
public fun foo() {
possibleXyz?.let { value ->
val surelyXyz: Xyz = value
}
}
阅读更多:安全调用
更多关于?:
Elvis运算符
当运算符左侧的表达式为null
时,Elvis运算符允许你提供一个替代值:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
它还有一些创意用途,例如在某些东西为null
时抛出异常:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
或者从函数中提前返回:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
return endingValue
}
阅读更多:Elvis运算符
与相关函数一起使用的 Null 运算符
Kotlin 标准库提供了一系列与上述运算符非常配合的函数。例如:
val something = possibleNull?.let { it.transform() } ?: defaultSomething
possibleNull?.apply {
func1()
func2()
}
val something = name.takeIf { it.isNotBlank() } ?: defaultName
val something = name.takeUnless { it.isBlank() } ?: defaultName
相关主题
Kotlin中,大多数应用程序都试图避免null
值,但有时不可避免。有时null
是完全合理的。以下是一些需要考虑的指南:
在某些情况下,需要不同的返回类型,包括方法调用的状态和成功时的结果。像
Result这样的库可以给你一个成功或失败的结果类型,也可以分支你的代码。而Kotlin的Promises库
Kovenant以promise的形式提供相同的功能。
对于集合作为返回类型,除非你需要第三种“不存在”的状态,否则总是返回一个空集合而不是
null
。 Kotlin有一些辅助函数,如
emptyList()
或emptySet()
来创建这些空值。
当使用返回可为空值的方法时,如果你有默认值或备选项,请使用Elvis运算符提供默认值。在
Map
的情况下,请使用
getOrElse()
,它允许生成默认值,而不是返回可为空值的
Map
方法
get()
。对于
getOrPut()
也是如此。
当覆盖Java中不确定Java代码的可空性的方法时,如果您确定签名和功能应该是什么,可以始终从覆盖中删除
?
可空性。因此,您的覆盖方法更加安全。实现Kotlin中的Java接口时也是如此,将可空性更改为您知道有效的内容。
查看已有的函数是否有帮助,例如
String?.isNullOrEmpty()
和
String?.isNullOrBlank()
,它们可以安全地操作可为空的值并执行您所期望的操作。事实上,您可以添加自己的扩展来填补标准库中的任何空白。
在标准库中,有像
checkNotNull()
和
requireNotNull()
这样的断言函数。
还有像
filterNotNull()
这样的辅助函数,它可以从集合中删除
null
,或者像
listOfNotNull()
这样的函数,可以从可能的
null
值返回零个或单个项目列表。
还有一个
安全(可为空)转换运算符,如果不可能,允许将类型转换为非空类型返回null。但我没有一个有效的用例,不能通过上述方法解决。