如果我有一个可为空的类型Xyz?,我想引用它或将其转换为不可为空的类型Xyz。在 Kotlin 中这样做的惯用方式是什么?
Xyz?
Xyz
例如,这段代码是错误的:
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() }
假设我确定它确实是 never ,我如何在null不需要检查的情况下更改或处理一个值?例如,在这里我从一个我可以保证存在的映射中检索一个值,并且结果is not 。但我有一个错误:if``null``get()``null
null
if``null``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()认为该项目可能丢失并返回 type Int?。因此,强制值的类型不可为空的最佳方法是什么?
get()
Int?
注意: 这个问题是作者有意编写和回答的,因此常见的 Kotlin 主题的惯用答案出现在 SO 中。还要澄清一些为 Kotlin 的 alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 并不准确。
首先,您应该阅读所有关于Kotlin 中的Null Safety的内容,其中全面涵盖了这些案例。
在 Kotlin 中,您不能访问一个可为空的值,除非确定它不是null(在条件中检查 null),或者断言它肯定没有null使用!!确定运算符,使用?.安全调用访问它,或者最后给出可能null是使用?:Elvis Operator的默认值。
!!
?.
?:
对于您的问题中的第一种情况,您 可以根据代码的意图使用其中一个选项,并且所有选项都是惯用的,但结果不同:
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,请使用!!确定运算符作为断言:
Map
val map = mapOf("a" to 65,"b" to 66,"c" to 67) val something = map.get("a")!! something.toLong() // now valid
或者在另一种情况下,当地图可以返回 null 但您可以提供默认值时,Map它本身就有一个getOrElse方法:
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检查保护对可空类型的访问,编译器将智能地将语句主体内的值强制转换为不可空。有一些复杂的流程不会发生这种情况,但对于常见的情况可以正常工作。
val possibleXyz: Xyz? = ... if (possibleXyz != null) { // allowed to reference members: possiblyXyz.foo() // or also assign as non-nullable type: val surelyXyz: Xyz = possibleXyz }
或者,如果您is检查不可为空的类型:
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检查进行智能转换以供以后使用变量。上面的示例使用了一个局部变量,该变量绝不可能在应用程序流程中发生变异,无论val此var变量是否有机会变异为null. 但是,在编译器无法保证流分析的其他情况下,这将是一个错误:
val
var
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检查不能智能转换为不可为空的值。有关解决方法,请参阅下面的“安全呼叫”主题。
nullableInt
智能强制转换不信任的另一种情况是val具有自定义 getter 的对象上的属性。在这种情况下,编译器无法看到改变值的原因,因此您将收到一条错误消息:
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解决此问题的函数:
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 }
阅读更多: 猫王操作员
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 的 Promises 库称为Kovenant以 Promise的形式做同样的事情。
作为返回类型的集合总是返回一个空集合而不是 a null,除非您需要“不存在”的第三种状态。Kotlin 具有辅助函数,例如创建这些空值的emptyList()或。emptySet()
emptyList()
emptySet()
当使用返回可空值且您有默认值或替代值的方法时,请使用 Elvis 运算符提供默认值。在Map使用的情况下getOrElse(),允许生成默认值而不是返回可空值的Map方法。get()相同的getOrPut()
getOrElse()
getOrPut()
当 Kotlin 不确定 Java 代码的可空性从 Java 覆盖方法时,?如果您确定签名和功能应该是什么,则始终可以从覆盖中删除可空性。因此,您的覆盖方法更null安全。在 Kotlin 中实现 Java 接口也是如此,将可空性更改为您所知道的有效。
?
查看已经可以提供帮助的函数,例如 forString?.isNullOrEmpty()并且String?.isNullOrBlank()可以安全地对可为空值进行操作并执行您期望的操作。事实上,您可以添加自己的扩展来填补标准库中的任何空白。
String?.isNullOrEmpty()
String?.isNullOrBlank()
断言函数checkNotNull()与requireNotNull()标准库中的 和 类似。
checkNotNull()
requireNotNull()
辅助函数,例如filterNotNull()从集合中删除空值,或listOfNotNull()从可能的值返回零或单个项目列表null。
filterNotNull()
listOfNotNull()
还有一个安全(可为空)强制转换运算符,如果不可能,它允许强制转换为不可为空的类型返回 null。但是我没有一个有效的用例,上面提到的其他方法没有解决这个问题。