当前位置: 首页 > 知识库问答 >
问题:

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

段兴为
2023-03-14

如果我有一个可为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() 
}

假设我确实知道一个值是nevernull,那么我如何更改或将它视为notnull而不需要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?。因此,强制值的类型不可为空的最佳方法是什么?

注意:这个问题是有意写的,并由作者回答(自我回答的问题),以便在so中出现对常见的Kotlin主题的惯用答案。同时也要澄清一些为阿尔法斯的Kotlin写的真正古老的答案,这些答案对于现在的Kotlin来说是不准确的。

共有2个答案

胡致远
2023-03-14

前面的回答很难理解,但这里有一个简单快捷的方法:

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

如果它真的从来不是空的,那么异常就不会发生,但是如果它真的是空的,您就会看到哪里出了问题。

景永望
2023-03-14

首先,您应该阅读Kotlin中关于空安全的所有内容,它全面地涵盖了这些案例。

在Kotlin中,如果不能确定可为null的值不是null(在条件中检查是否为null),或者不能使用!!sure运算符断言它肯定不是null,使用?.Safe调用访问它,或者最后使用?: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() 
}

关于“当选中null时它为什么起作用”,请阅读下面关于智能强制转换的背景信息。

对于使用映射的问题中的第二种情况,如果您作为开发人员确信结果不会为null,请使用!!sure运算符作为断言:

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

或者在另一种情况下,当映射可以返回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类型的访问,编译器将智能强制转换语句正文中的值为不可为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检查智能强制转换,以便稍后使用变量。上面的示例使用了一个局部变量,该变量在应用程序流中绝不可能发生突变,无论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检查不能智能转换为不可为空的值。有关解决方法,请参阅下面的“安全呼叫”主题。

智能强制转换不能相信不会发生突变的另一种情况是具有自定义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 'p.x' is a property that has open or custom getter"
   thing.possiblyXyz.foo()
}

阅读更多信息:检查条件中的null

如果左侧的值为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且不为空的情况下,您才希望迭代列表,safe call运算符也会派上用场:

val things: List? = makeMeAListOrDont()
things?.forEach {
    // this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}

在上面的一个例子中,我们做了if检查,但有可能另一个线程改变了值,因此没有智能强制转换。我们可以将此示例更改为使用safe call运算符以及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的承诺库Kovenant以承诺的形式做同样的工作。

    for集合作为返回类型总是返回空集合,而不是null,除非您需要第三种状态“not present”。Kotlin有诸如emptylist()emptyset()之类的帮助器函数来创建这些空值。

    当使用返回具有默认值或备选值的可空值的方法时,请使用Elvis运算符提供默认值。对于map使用允许生成默认值的getorelse()方法,而不是返回可为空值的map方法get()getorput()与此相同

    当重写来自Kotlin不确定Java代码的可为空性的Java方法时,如果您确定签名和功能应该是什么,则始终可以从重写中删除可为空性。因此,重写的方法更加null安全。同样,在Kotlin中实现Java接口,将nullability更改为您知道是有效的。

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

    标准库中的断言函数如checknotnull()requireNotnull()

    帮助程序函数,如FilterNotNull()从集合中删除Null,或ListOfNotNull()从可能的Null值返回零或单个项列表。

    还有一个安全的(可为null的)强制转换运算符,它允许强制转换为不可为null的类型,如果不可能,则返回null。但是,我没有一个有效的用例可以通过上面提到的其他方法解决。

  •  类似资料:
    • 问题内容: 如何在Java中构建URL或URI?有没有惯用的方法,还是图书馆很容易做到这一点? 我需要允许从请求字符串开始,解析/更改各种URL部分(方案,主机,路径,查询字符串),并支持添加和自动编码查询参数。 问题答案: 使用HTTPClient效果很好。

    • 问题内容: 在SQL查询中将int或null转换为布尔值的最佳方法是什么,例如: 任何非空值为 TRUE 的结果 任何空值是 假 的结果 问题答案: 据我所知(如果我错了,请纠正我),SQL中没有文字布尔值的概念。您可以使表达式的计算结果为布尔值,但不能输出它们。 就是说,您可以使用CASE WHEN来产生可以在比较中使用的值:

    • 问题内容: 做以下事情的最惯用的方法是什么? 更新: 我合并了Tryptich的建议使用str(s),这使该例程可用于除字符串以外的其他类型。Vinay Sajip的lambda建议给我留下了深刻的印象,但是我想保持我的代码相对简单。 问题答案: 如果您实际上希望函数的行为类似于内置函数,但是在参数为None时返回空字符串,请执行以下操作:

    • 问题内容: 我尝试阅读一些文章,但对这个主题不太清楚。 有人想向我解释以下几点: 为什么通过HTTP使用websocket 什么是全双工通信 延迟延迟交互是什么意思 问题答案: 为什么通过HTTP使用websocket? webSocket是客户端和服务器之间的连续连接。该连续连接允许以下操作: 数据可以随时从服务器发送到客户端,而客户端甚至不需要请求。这通常称为服务器推送,对于客户端需要在服务器

    • 我试图表示一个简化的染色体,它由N个碱基组成,每个碱基只能是中的一个。 我想用枚举形式化约束,但我想知道在Go中模拟枚举的最惯用的方式是什么。

    • 下面是我使用jackson转换为pojo的json响应- 代码: 我收到以下例外: 线程“main”com . faster XML . Jackson . databind . jsonmappingexception中出现异常:无法从字符串值(“”)实例化类型为[simple type,class com . test . sample . paymenttyperpons]的值;没有单字符串