在 Kotlin 中,处理可为空值、引用或转换它们的惯用方法是什么? [英] In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them

查看:24
本文介绍了在 Kotlin 中,处理可为空值、引用或转换它们的惯用方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我有一个可空类型 Xyz?,我想引用它或将它转换为不可空类型 Xyz.在 Kotlin 中这样做的惯用方式是什么?

If I have a nullable type Xyz?, I want to reference it or convert it to a non-nullable type Xyz. What is the idiomatic way of doing so in Kotlin?

例如这段代码是错误的:

For example, this code is in error:

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

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

But if I check null first it is allowed, why?

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

如何更改或将值视为非 null 而不需要 if 检查,假设我确定它真的永远不会 null>?例如,这里我从我可以保证存在的映射中检索一个值,并且 get() 的结果不是 null.但我有一个错误:

How do I change or treat a value as not null without requiring the if check, assuming I know for sure it is truly never null? For example, here I am retrieving a value from a map that I can guarantee exists and the result of get() is not null. But I have an error:

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?.因此,强制值的类型不可为空的最佳方法是什么?

The method get() thinks it is possible that the item is missing and returns type Int?. Therefore, what is the best way to force the type of the value to be not nullable?

注意:这个问题是作者有意编写和回答的(Self-Answered Questions),以便常见问题的 Kotlin 主题的惯用答案出现在 SO 中.还要澄清一些为 Kotlin alpha 编写的非常旧的答案,这些答案对于当前的 Kotlin 来说是不准确的.

Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO. Also to clarify some really old answers written for alphas of Kotlin that are not accurate for current-day Kotlin.

推荐答案

首先,你应该阅读所有关于 Kotlin 中的空安全,它彻底涵盖了这些情况.

First, you should read all about Null Safety in Kotlin which covers the cases thoroughly.

在 Kotlin 中,如果不能确定它不是 null(检查条件中的 null),或者使用 null="https://kotlinlang.org/docs/reference/null-safety.html#the--operator" rel="noreferrer">!! 确定运算符,使用一个 ?. 安全调用,或者最后使用 ?: Elvis Operator.

In Kotlin, you cannot access a nullable value without being sure it is not 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.

对于问题中的第一种情况,根据代码的意图,您可以选择使用其中一种,并且所有选项都是惯用的,但结果不同:

For your 1st case in your question you have options depending on the intent of the code you would use one of these, and all are idiomatic but have different results:

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

对于检查空值时为什么会起作用",请阅读下面关于 聪明的演员.

For the "Why does it work when null checked" read the background information below on smart casts.

对于Map 问题中的第二种情况,如果您作为开发人员确定结果永远不会null, 使用 !! 确定操作符作为断言:

For your 2nd case in your question in the question with Map, if you as a developer are sure of the result never being null, use !! sure operator as an assertion:

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

或者在另一种情况下,当地图可以返回空值但您可以提供默认值时,Map 本身就有一个 getOrElse 方法:

or in another case, when the map COULD return a null but you can provide a default value, then Map itself has a 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


背景信息:

注意: 在下面的示例中,我使用显式类型来明确行为.使用类型推断,通常可以省略局部变量和私有成员的类型.

!! 运算符断言该值不是 null 或抛出 NPE.这应该在开发人员保证值永远不会为 null 的情况下使用.把它想象成一个断言,然后是一个 smart cast.

The !! operator asserts that the value is not null or throws an NPE. This should be used in cases where the developer is guaranteeing that the value will never be null. Think of it as an assert followed by a 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()

阅读更多:!!确定运算符

如果您使用 null 检查保护对可空类型的访问,编译器将 智能转换 语句主体内的值不可为空.有一些复杂的流程不会发生这种情况,但对于一般情况下可以正常工作.

If you protect access to a nullable type with a null check, the compiler will smart cast the value within the body of the statement to be non-nullable. There are some complicated flows where this cannot happen, but for common cases works fine.

val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}

或者,如果您对不可为空的类型进行 is 检查:

Or if you do a is check for a non-nullable type:

if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}

同样适用于同样安全转换的 'when' 表达式:

And the same for 'when' expressions that also safe cast:

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.但是,在编译器无法保证流分析的其他情况下,这将是一个错误:

Some things do not allow the null check to smart cast for the later use of the variable. The example above uses a local variable that in no way could have mutated in the flow of the application, whether val or var this variable had no opportunity to mutate into a null. But, in other cases where the compiler cannot guarantee the flow analysis, this would be an error:

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 为不可为空的值.请参阅下面的安全呼叫"主题以了解解决方法.

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.

智能演员无法信任的另一种情况mutate 是具有自定义 getter 的对象上的 val 属性.在这种情况下,编译器无法了解是什么改变了值,因此您将收到一条错误消息:

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

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

阅读更多:检查条件中的空值

如果左边的值为空,安全调用运算符返回空,否则继续计算右边的表达式.

The safe call operator returns null if the value to the left is null, otherwise continues to evaluate the expression to the right.

val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()

另一个例子,你想迭代一个列表,但前提是不是 null 并且不为空,安全调用运算符再次派上用场:

Another example where you want to iterate a list but only if not null and not empty, again the safe call operator comes in handy:

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 函数来解决此问题:

In one of the examples above we had a case where we did an if check but have the chance another thread mutated the value and therefore no smart cast. We can change this sample to use the safe call operator along with the let function to solve this:

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 时提供替代值:

The Elvis operator allows you to provide an alternative value when an expression to the left of the operator is null:

val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()

它也有一些创造性的用途,例如当某事物为 null 时抛出异常:

It has some creative uses as well, for example throw an exception when something is 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 有一系列函数,可以很好地与上述运算符配合使用.例如:

Kotlin stdlib has a series of functions that work really nicely with the operators mentioned above. For example:

// 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 很有意义.一些需要考虑的准则:


Related Topics

In Kotlin, most applications try to avoid null values, but it isn't always possible. And sometimes null makes perfect sense. Some guidelines to think about:

  • 在某些情况下,它保证不同的返回类型,包括方法调用的状态和成功的结果.Result 之类的库为您提供成功或失败结果类型,也可以分支您的代码.Kotlin 的 Promises 库名为 Kovenant 以 Promise 的形式执行相同的操作.

  • in some cases, it warrants different return types that include the status of the method call and the result if successful. Libraries like Result give you a success or failure result type that can also branch your code. And the Promises library for Kotlin called Kovenant does the same in the form of promises.

对于作为返回类型的集合总是返回一个空集合而不是一个 null,除非您需要不存在"的第三个状态.Kotlin 有诸如 emptyList()emptySet() 来创建这些空值.

for collections as return types always return an empty collection instead of a null, unless you need a third state of "not present". Kotlin has helper functions such as emptyList() or emptySet() to create these empty values.

当使用返回可空值且您有默认值或替代值的方法时,请使用 Elvis 运算符来提供默认值.对于 Map 使用 getOrElse() 允许生成默认值而不是 Map 方法 get() 返回可空值.getOrPut()

when using methods which return a nullable value for which you have a default or alternative, use the Elvis operator to provide a default value. In the case of a Map use the 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 接口相同,将可空性更改为您所知道的有效.

when overriding methods from Java where Kotlin isn't sure about the nullability of the Java code, you can always drop the ? nullability from your override if you are sure what the signature and functionality should be. Therefore your overridden method is more null safe. Same for implementing Java interfaces in Kotlin, change the nullability to be what you know is valid.

查看已经可以提供帮助的函数,例如 String?.isNullOrEmpty()String?.isNullOrBlank() 可以安全地对可空值进行操作并执行您期望的操作.事实上,您可以添加自己的扩展来填补标准库中的任何空白.

look at functions that can help already, such as for String?.isNullOrEmpty() and String?.isNullOrBlank() which can operate on a nullable value safely and do what you expect. In fact, you can add your own extensions to fill in any gaps in the standard library.

断言函数,如 checkNotNull()requireNotNull() 在标准库中.

assertion functions like checkNotNull() and requireNotNull() in the standard library.

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

helper functions like filterNotNull() which remove nulls from collections, or listOfNotNull() for returning a zero or single item list from a possibly null value.

有一个 安全(可为空)转换运算符 以及允许强制转换为不可为空的类型,如果不可能,则返回 null.但是我没有一个有效的用例来解决上述其他方法无法解决的问题.

there is a Safe (nullable) cast operator as well that allows a cast to non-nullable type return null if not possible. But I do not have a valid use case for this that isn't solved by the other methods mentioned above.

这篇关于在 Kotlin 中,处理可为空值、引用或转换它们的惯用方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆