为什么在设置值和检查是否为空后,可空值的智能转换失败?

3
假设我有以下类:
class MyClass {
    private var username: String? = null
    private var projectName: String? = null
    private var buildNumber: Int = -1
    private val presenter: Presenter = Presenter()

    fun present() {
        username = ""
        projectName = ""

        if (username != null && projectName != null && buildNumber != -1) {
            presenter.viewReady(this, username, projectName, buildNumber)
        } else {
            throw Exception("You did something bad!")
        }
    }
}

为什么我会收到错误消息“智能转换为'String'是不可能的,因为'username'是一个可变属性,可能已经被更改了”?
这是否与线程安全有关?
根据null safety docs,我认为以下两种情况之一应该可以解决此问题:1. 在使用它们作为参数之前,在同一个函数中设置usernameprojectName,2. 将它们用作参数时,将其包装在检查其值的if语句中。
2个回答

7
Kotlin编译器无法证明usernameprojectName是否同时被另一个线程修改。即使该字段是私有的,反射也可能会绕过这个限制。
参考文档在类型检查和类型转换
请注意,当编译器无法保证变量在检查和使用之间不会更改时,智能转换将不起作用。具体来说,智能转换适用于以下规则:
- val 局部变量-总是适用; - val 属性 - 如果属性是 private 或 internal,或者在声明该属性的同一模块中执行检查,则适用智能转换。开放属性或自定义 getter 的属性不适用智能转换; - var 局部变量 - 如果在检查和使用之间未修改变量,并且未在修改变量的 lambda 中捕获,则适用智能转换; - var 属性-永远不适用(因为变量可以随时被其他代码修改)。
请将属性引用捕获到局部变量中。
if语句在Kotlin中不会“捕获”属性。当您声明包含属性的if语句并在块内再次访问它时,编译器可能会为您智能转换。但对于访问规则仍然相同- getter 方法将被调用两次。

1
此外,如果您只有一个变量存在此问题,您可以使用 username?.let {},其中内部的 it 将是非空类型,因为 let 保留了它被调用时的值,忽略了实际变量的任何修改。但是,在此嵌套执行此操作并不美观。 - zsmb13
最简单的修复方法是使用非空断言运算符,因为您可以保证这里不会发生任何错误:presenter.viewReady(this, username!!, projectName!!, buildNumber) - voddan
@voddan 这可能是最简单的解决方法,但习惯使用 !! 会在以后导致难以调试的问题。 - F. George
好的,让我重新表述一下 - 有时使用 !! 是解决问题的正确方式,我会在我的答案中证明它。 - voddan

2
正如@mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu所指出的,Kotlin不会为var属性激活智能转换。我不太确定为什么选择了这种策略,因为在预发布版本中,private var可以被智能转换,但这就是我们现在所面临的问题。
现在假设您确定那些属性没有被另一个线程修改,因为它们是private并且没有使用反射。因此,if-null检查确保属性不包含null,但是Kotlin编译器并不相信。
在这种情况下,我强烈建议使用非空断言运算符!!
presenter.viewReady(this, username!!, projectName!!, buildNumber)

许多人建议避免使用!!来改善编程风格,但它的含义字面上是"编译器很蠢,这个不可能是null,如果我错了通知我"。像您这样的情况正是引入该语言的原因。

还有其他的解决方法,包括将属性的当前值保存到本地val中(明确或隐式通过帮助函数),但我认为它们在这里不合适,因为它们不能表达您的意图。在某些情况下,需要保留当前值并进行操作的语义,但在这里,需要让编译器相信代码的语义,而这两者显然不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接