在 Kotlin 中,处理可空值、引用或转换它们的惯用方法是什么
In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them
如果我有一个可为 null 的类型 Xyz?
,我想引用它或将其转换为一个不可为 null 的类型 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()
}
如何在不需要 if
检查的情况下将值更改或视为非 null
,假设我确定它确实永远不会 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()
认为可能是物品丢失,returns输入Int?
。因此,强制值的类型不可为空的最佳方法是什么?
注意: 这个问题是作者(Self-Answered Questions)特意写下并回答的,以便对常见的 Kotlin 主题进行地道的回答存在于 SO 中。还要澄清一些为 Kotlin alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 来说并不准确。
首先,您应该阅读有关 Null Safety in Kotlin 的所有内容,其中涵盖了所有案例。
在 Kotlin 中,您不能在不确定它不是 null
(Checking for null in conditions), or asserting that it is surely not null
using the !!
sure operator, accessing it with a ?.
Safe Call, or lastly giving something that is possibly null
a default value using the ?:
Elvis Operator.
的情况下访问可为 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()
}
关于“为什么检查 null 时它起作用”请阅读下面关于 smart casts 的背景信息。
对于你问题中的第二种情况,如果你作为开发人员确定结果永远不会是 null
,使用 !!
确定运算符作为断言:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
或者在另一种情况下,当映射可以 return 为 null 但您可以提供默认值时,则 Map
本身具有 getOrElse
method:
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
背景资料:
注意: 在下面的示例中,我使用显式类型来使行为清晰。使用类型推断,通常可以省略局部变量和私有成员的类型。
有关 !!
sure 运算符的更多信息
!!
运算符断言该值不是 null
或抛出 NPE。这应该在开发人员保证该值永远不会是 null
的情况下使用。将其视为后跟 smart cast.
的断言
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()
阅读更多:!! Sure Operator
有关 null
检查和智能转换的更多信息
如果您使用 null
检查保护对可空类型的访问,编译器将 smart cast 语句主体中的值设为 non-nullable。有一些复杂的流程是不可能发生这种情况的,但对于常见情况来说效果很好。
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
或者如果您执行 is
检查 non-nullable 类型:
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
检查到 smart cast 以便以后使用变量。上面的示例使用了一个局部变量,该变量绝不会在应用程序的流程中发生变异,无论是 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
检查不能smart cast变成non-nullable值。请参阅下面的“安全呼叫”主题以获取解决方法。
另一种 smart cast 不信任的情况是 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()
}
阅读更多:Checking for null in conditions
有关 ?.
安全呼叫运营商的更多信息
安全调用运算符return如果左边的值为空,则为空,否则继续计算右边的表达式。
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
检查,但有机会另一个线程改变了值,因此没有 smart cast。我们可以更改此示例以使用安全调用运算符以及 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
}
}
阅读更多:Safe Calls
有关 ?:
猫王运算符的更多信息
Elvis 运算符允许您在运算符左侧的表达式为 null
:
时提供替代值
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
它也有一些创造性的用途,例如在 null
:
时抛出异常
val currentUser = session.user ?: throw Http401Error("Unauthorized")
或 return 从一个函数开始:
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
非常有意义。需要考虑的一些准则:
在某些情况下,它保证了不同的 return 类型,包括方法调用的状态和成功时的结果。像 Result give you a success or failure result type that can also branch your code. And the Promises library for Kotlin called Kovenant 这样的库以 promises 的形式做同样的事情。
对于 return 类型的集合总是 return 空集合而不是 null
,除非你需要第三种状态“不存在”。 Kotlin 有辅助函数,例如 emptyList()
or emptySet()
来创建这些空值。
当使用 return 具有默认值或替代值的可空值的方法时,请使用 Elvis 运算符提供默认值。在 Map
的情况下使用 getOrElse()
which allows a default value to be generated instead of Map
method get()
which returns a nullable value. Same for getOrPut()
当覆盖 Java 的方法时,Kotlin 不确定 Java 代码的可空性,您始终可以从您的代码中删除 ?
可空性如果您确定签名和功能应该是什么,则覆盖。因此,您的覆盖方法更 null
安全。与在 Kotlin 中实现 Java 接口相同,将可空性更改为您所知道的有效。
查看已经可以提供帮助的函数,例如 String?.isNullOrEmpty()
and String?.isNullOrBlank()
,它可以安全地对可为 null 的值进行操作并执行您期望的操作。事实上,您可以添加自己的扩展来填补标准库中的任何空白。
断言函数类似于标准库中的 checkNotNull()
and requireNotNull()
。
helper 功能类似于 filterNotNull()
which remove nulls from collections, or listOfNotNull()
,用于从可能的 null
值 returning 零项或单个项列表。
还有一个 Safe (nullable) cast operator 允许转换为 non-nullable 类型 return null 如果不可能。但是我没有一个有效的用例,上面提到的其他方法没有解决这个问题。
之前的答案很难遵循,但这里有一个快速简便的方法:
val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo()
如果它真的永远不会为 null,则不会发生异常,但如果确实为 null,您就会看到哪里出了问题。
我想补充一点,现在存在 Konad 库,可以解决可空组合的更复杂情况。下面是一个示例用法:
val foo: Int? = 1
val bar: String? = "2"
val baz: Float? = 3.0f
fun useThem(x: Int, y: String, z: Float): Int = x + y.toInt() + z.toInt()
val result: Int? = ::useThem.curry()
.on(foo.maybe)
.on(bar.maybe)
.on(baz.maybe)
.nullable
如果你想保持它可以为空,或者
val result: Result<Int> = ::useThem.curry()
.on(foo.ifNull("Foo should not be null"))
.on(bar.ifNull("Bar should not be null"))
.on(baz.ifNull("Baz should not be null"))
.result
如果你想累积错误。参见 maybe section
接受的答案包含完整的细节,我在这里添加摘要
如何在可空类型的变量上调用函数
val str: String? = "HELLO"
// 1. Safe call (?), makes sure you don't get NPE
val lowerCaseStr = str?.toLowerCase() // same as str == null ? null : str.toLowerCase()
// 2. non-null asserted call (!!), only use if you are sure that value is non-null
val upperCaseStr = str!!.toUpperCase() // same as str.toUpperCase() in java, NPE if str is null
如何将可空类型变量转换为不可空类型
假定您 100% 确定可空变量包含非空值
// use non-null assertion, will cause NPE if str is null
val nonNullableStr = str!! // type of nonNullableStr is String(non-nullable)
为什么空检查块中不需要安全(?)或非空(!!)断言
如果编译器可以guarantee变量在检查和使用之间不会改变,那么它知道变量不可能为空,所以你可以
if(str != null){
val upperCaseStr = str.toUpperCase() // str can't possibly be null, no need of ? or !!
}
如果我有一个可为 null 的类型 Xyz?
,我想引用它或将其转换为一个不可为 null 的类型 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()
}
如何在不需要 if
检查的情况下将值更改或视为非 null
,假设我确定它确实永远不会 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()
认为可能是物品丢失,returns输入Int?
。因此,强制值的类型不可为空的最佳方法是什么?
注意: 这个问题是作者(Self-Answered Questions)特意写下并回答的,以便对常见的 Kotlin 主题进行地道的回答存在于 SO 中。还要澄清一些为 Kotlin alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 来说并不准确。
首先,您应该阅读有关 Null Safety in Kotlin 的所有内容,其中涵盖了所有案例。
在 Kotlin 中,您不能在不确定它不是 null
(Checking for null in conditions), or asserting that it is surely not null
using the !!
sure operator, accessing it with a ?.
Safe Call, or lastly giving something that is possibly null
a default value using the ?:
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 时它起作用”请阅读下面关于 smart casts 的背景信息。
对于你问题中的第二种情况,如果你作为开发人员确定结果永远不会是 null
,使用 !!
确定运算符作为断言:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
或者在另一种情况下,当映射可以 return 为 null 但您可以提供默认值时,则 Map
本身具有 getOrElse
method:
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
背景资料:
注意: 在下面的示例中,我使用显式类型来使行为清晰。使用类型推断,通常可以省略局部变量和私有成员的类型。
有关 !!
sure 运算符的更多信息
!!
运算符断言该值不是 null
或抛出 NPE。这应该在开发人员保证该值永远不会是 null
的情况下使用。将其视为后跟 smart cast.
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()
阅读更多:!! Sure Operator
有关 null
检查和智能转换的更多信息
如果您使用 null
检查保护对可空类型的访问,编译器将 smart cast 语句主体中的值设为 non-nullable。有一些复杂的流程是不可能发生这种情况的,但对于常见情况来说效果很好。
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
或者如果您执行 is
检查 non-nullable 类型:
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
检查到 smart cast 以便以后使用变量。上面的示例使用了一个局部变量,该变量绝不会在应用程序的流程中发生变异,无论是 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
检查不能smart cast变成non-nullable值。请参阅下面的“安全呼叫”主题以获取解决方法。
另一种 smart cast 不信任的情况是 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()
}
阅读更多:Checking for null in conditions
有关 ?.
安全呼叫运营商的更多信息
安全调用运算符return如果左边的值为空,则为空,否则继续计算右边的表达式。
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
检查,但有机会另一个线程改变了值,因此没有 smart cast。我们可以更改此示例以使用安全调用运算符以及 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
}
}
阅读更多:Safe Calls
有关 ?:
猫王运算符的更多信息
Elvis 运算符允许您在运算符左侧的表达式为 null
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
它也有一些创造性的用途,例如在 null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
或 return 从一个函数开始:
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
非常有意义。需要考虑的一些准则:
在某些情况下,它保证了不同的 return 类型,包括方法调用的状态和成功时的结果。像 Result give you a success or failure result type that can also branch your code. And the Promises library for Kotlin called Kovenant 这样的库以 promises 的形式做同样的事情。
对于 return 类型的集合总是 return 空集合而不是
null
,除非你需要第三种状态“不存在”。 Kotlin 有辅助函数,例如emptyList()
oremptySet()
来创建这些空值。当使用 return 具有默认值或替代值的可空值的方法时,请使用 Elvis 运算符提供默认值。在
Map
的情况下使用getOrElse()
which allows a default value to be generated instead ofMap
methodget()
which returns a nullable value. Same forgetOrPut()
当覆盖 Java 的方法时,Kotlin 不确定 Java 代码的可空性,您始终可以从您的代码中删除
?
可空性如果您确定签名和功能应该是什么,则覆盖。因此,您的覆盖方法更null
安全。与在 Kotlin 中实现 Java 接口相同,将可空性更改为您所知道的有效。查看已经可以提供帮助的函数,例如
String?.isNullOrEmpty()
andString?.isNullOrBlank()
,它可以安全地对可为 null 的值进行操作并执行您期望的操作。事实上,您可以添加自己的扩展来填补标准库中的任何空白。断言函数类似于标准库中的
checkNotNull()
andrequireNotNull()
。helper 功能类似于
filterNotNull()
which remove nulls from collections, orlistOfNotNull()
,用于从可能的null
值 returning 零项或单个项列表。还有一个 Safe (nullable) cast operator 允许转换为 non-nullable 类型 return null 如果不可能。但是我没有一个有效的用例,上面提到的其他方法没有解决这个问题。
之前的答案很难遵循,但这里有一个快速简便的方法:
val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo()
如果它真的永远不会为 null,则不会发生异常,但如果确实为 null,您就会看到哪里出了问题。
我想补充一点,现在存在 Konad 库,可以解决可空组合的更复杂情况。下面是一个示例用法:
val foo: Int? = 1
val bar: String? = "2"
val baz: Float? = 3.0f
fun useThem(x: Int, y: String, z: Float): Int = x + y.toInt() + z.toInt()
val result: Int? = ::useThem.curry()
.on(foo.maybe)
.on(bar.maybe)
.on(baz.maybe)
.nullable
如果你想保持它可以为空,或者
val result: Result<Int> = ::useThem.curry()
.on(foo.ifNull("Foo should not be null"))
.on(bar.ifNull("Bar should not be null"))
.on(baz.ifNull("Baz should not be null"))
.result
如果你想累积错误。参见 maybe section
接受的答案包含完整的细节,我在这里添加摘要
如何在可空类型的变量上调用函数
val str: String? = "HELLO"
// 1. Safe call (?), makes sure you don't get NPE
val lowerCaseStr = str?.toLowerCase() // same as str == null ? null : str.toLowerCase()
// 2. non-null asserted call (!!), only use if you are sure that value is non-null
val upperCaseStr = str!!.toUpperCase() // same as str.toUpperCase() in java, NPE if str is null
如何将可空类型变量转换为不可空类型
假定您 100% 确定可空变量包含非空值
// use non-null assertion, will cause NPE if str is null
val nonNullableStr = str!! // type of nonNullableStr is String(non-nullable)
为什么空检查块中不需要安全(?)或非空(!!)断言
如果编译器可以guarantee变量在检查和使用之间不会改变,那么它知道变量不可能为空,所以你可以
if(str != null){
val upperCaseStr = str.toUpperCase() // str can't possibly be null, no need of ? or !!
}