在Kotlin中,处理可空值的惯用方式是什么,引用或转换它们

如果我有一个可空类型Xyz? ,我想引用它或将其转换为不可为空的类型Xyz 。 Kotlin这样做的习惯用法是什么?

例如,这个代码是错误的:

 val something: Xyz? = createPossiblyNullXyz() something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?" 

但是,如果我先检查null是允许的,为什么呢?

 val something: Xyz? = createPossiblyNullXyz() if (something != null) { something.foo() } 

假如我知道它确实从不为null ,我该如何将值更改或视为非null而不要求if检查? 例如,在这里我从地图中检索一个值,我可以保证存在,并且get()的结果不为null 。 但是我有一个错误:

 val map = mapOf("a" to 65,"b" to 66,"c" to 67) val something = map.get("a") something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?" 

方法get()认为有可能该项目丢失,并返回类型Int? 。 因此,强制价值类型不能为空的最好方法是什么?

注意: 这个问题是由作者故意写的和回答的( 自我回答的问题 ),所以对于常见的Kotlin话题的习惯性的回答是在SO中。 此外,为了澄清一些真正的古老的答案写为科特林的阿尔法,是不是今天的Kotlin准确。

首先,你应该阅读所有关于Kotlin全面涵盖案例的Null Safety 。

在Kotlin中,如果不能确定它是否为null ( 在条件中检查为null ),或者断言它肯定不是null的, 确定操作员 ,用?.访问它?. 安全调用 ,或者最后使用?: Elvis运算符给出可能为null的默认值。

在你的第一个案例中,你可以根据代码的意图选择其中的一个,而且都是惯用的,但是有不同的结果:

 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() } 

对于“为什么在空值检查时工作”,请阅读下面关于智能转换的背景信息。

对于你在 Map 问题中的第二种情况 ,如果你作为一个开发人员肯定结果永远不是null ,请使用!! 肯定算作一个断言:

 val map = mapOf("a" to 65,"b" to 66,"c" to 67) val something = map.get("a")!! something.toLong() // now valid 

或者在另一种情况下,当映射COULD返回一个null值,但是你可以提供一个默认值,那么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'表达式也是安全的:

 when (possibleXyz) { null -> doSomething() else -> possibleXyz.foo() } // or when (possibleXyz) { is Xyz -> possibleXyz.foo() is Alpha -> possibleXyz.dominate() is Fish -> possibleXyz.swim() } 

有些事情不允许null检查智能强制转换为以后使用的变量。 上面的例子使用了一个本地变量,这个变量在应用程序的流程中决不可能发生变化,无论valvar这个变量是否没有机会变为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检查不能被智能转换为不可空值。 有关解决方法,请参阅下面的“安全调用”主题。

另一个智能强制转换不能被信任的情况是具有自定义getter的对象的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 'px' is a property that has open or custom getter" thing.possiblyXyz.foo() } 

阅读更多: 检查条件为空


更多关于?. 安全呼叫操作员

如果左边的值为空,则安全调用操作符返回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 } } 

阅读更多: 安全通话


更多关于?:猫王操作员

当运算符左侧的表达式为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 } 

阅读更多: 猫王操作员


具有相关函数的空操作符

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是非常有意义的。 一些指导思想:

  • 在某些情况下,它保证不同的返回类型,包括方法调用的状态和成功的结果。 像Result这样的库给你一个成功或失败的结果类型,也可以分支你的代码。 Kotlin的Promise库叫Kovenant,它的承诺形式也一样。

  • 对于集合来说,返回类型总是返回一个空集合,而不是null ,除非你需要第三个“不存在”状态。 Kotlin具有帮助函数,如emptyList()emptySet()来创建这些空值。

  • 当使用返回一个默认值或替代值的可为空值的方法时,使用Elvis操作符提供一个默认值。 在Map的情况下使用getOrElse() ,它允许生成一个默认值,而不是Map方法get() ,它返回一个可为空的值。 相同的getOrPut()

  • 当从Kotlin不确定Java代码的可空性的Java重写方法时,您总是可以放弃? 如果您确定签名和功能应该是什么,那么您可以忽略它。 因此,你重写的方法是更安全的。 在Kotlin中实现Java接口也是一样的,把它改为可以被认为是有效的。

  • 查看已经可以帮助的函数,比如对于String?.isNullOrEmpty()String?.isNullOrBlank() ,它们可以安全地操作一个可为null的值,并按照您的期望进行操作。 实际上,您可以添加自己的扩展来填充标准库中的任何空缺。

  • 断言函数,如标准库中的checkNotNull()requireNotNull()

  • filterNotNull()从集合中删除filterNotNull()帮助器函数,或者listOfNotNull()用于从可能的null值返回零或单个项目列表。

  • 还有一个安全(可为空)的转换操作符 ,如果不可能,则允许非空类型的转换返回null。 但是我没有一个有效的用例来解决上面提到的其他方法。

以前的答案是难以遵循的,但是这里有一个简单快捷的方法:

 val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null") something.foo() 

如果它真的不是空的,这个异常就不会发生,但如果它永远是你会发现什么地方出了问题。