首先,您应该阅读有关Kotlin中的Null Safety的全部内容,其中涵盖了所有案例。
在Kotlin中,您不能访问可为空的值,而不能确定它不是null
(检查条件中的null),或者断言它肯定不是在null
使用!!
sure操作符,通过?.
安全调用访问它,或者最后给出可能null
是使用?:
Elvis运算符的默认值。
对于您遇到的第一种情况,您可以根据代码的意图选择选项,可以使用以下选项之一,它们都是惯用的,但结果不同:
val something: Xyz? = createPossiblyNullXyz()
// access it as non-null asserting that with a sure call
val result1 = something!!.foo()
// access it only if it is not null using safe operator,
// returning null otherwise
val result2 = something?.foo()
// access it only if it is not null using safe operator,
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue
// null check it with `if` expression and then use the value,
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
something.foo()
} else {
...
differentValue
}
// null check it with `if` statement doing a different action
if (something != null) {
something.foo()
} else {
someOtherAction()
}
对于“为什么选中null时为什么起作用”,请阅读以下有关智能转换的背景信息。
对于您问题中带有的第二种情况Map
,如果您作为开发人员确信结果永远不会存在null
,请使用!!
sure运算符作为断言:
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? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!!
// same thing but access members after the assertion is made:
possibleXyz!!.foo()
阅读更多:!确定运算符
有关null
检查和智能投射的更多信息
如果您通过null
检查保护对可为空的类型的访问,则编译器将智能地将语句体内的值强制转换为不可为空。有些复杂的流程无法做到这一点,但在通常情况下效果很好。
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
或者,如果您is
检查非空类型:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
对于也可以强制转换的“何时”表达式也是如此:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
// or
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) {
// Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
val nonNullableInt: Int = nullableInt
}
}
变量的生命周期nullableInt
不是完全可见的,并且可以从其他线程分配,null
因此无法智能地将检查强制转换为不可为空的值。请参阅下面的“安全呼叫”主题以获取解决方法。
不能由智能转换信任而不进行突变的另一种情况是val
具有自定义吸气剂的对象的属性。在这种情况下,编译器无法查看更改值的原因,因此您将收到错误消息:
class MyThing {
val possibleXyz: Xyz?
get() { ... }
}
// now when referencing this class...
val thing = MyThing()
if (thing.possibleXyz != null) {
// error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
thing.possiblyXyz.foo()
}
阅读更多:检查条件是否为空
有关“ ?.
安全呼叫”操作员的更多信息
如果左侧的值为null,则安全调用运算符将返回null,否则继续评估右侧的表达式。
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
另一个要迭代列表但仅在不迭代null
且不为空的示例中,安全调用运算符再次派上用场:
val things: List? = makeMeAListOrDont()
things?.forEach {
// this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
在上面的一个例子中,我们进行了if
检查,但是有机会另一个线程改变了这个值,因此没有进行智能转换。我们可以更改此示例以使用安全调用运算符以及let
用于解决此问题的函数:
var possibleXyz: Xyz? = 1
public fun foo() {
possibleXyz?.let { value ->
// only called if not null, and the value is captured by the lambda
val surelyXyz: Xyz = value
}
}
了解更多:安全电话
有关?:
猫王操作员的更多信息
Elvis运算符允许您在运算符左侧的表达式为时提供一个替代值null
:
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 Operator
具有相关功能的空运算符
Kotlin stdlib具有一系列功能,可以与上述运算符很好地协同工作。例如:
// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething
// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
func1()
func2()
}
// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName
val something = name.takeUnless { it.isBlank() } ?: defaultName
相关话题
在Kotlin中,大多数应用程序都尝试避免使用null
值,但并非总是可能的。有时null
是很合理的。需要考虑的一些准则: