为什么智能强制转换在设置值并检查它是否为空之后失败为可空值?

时间:2017-04-23 02:52:46

标签: null kotlin

假设我有以下课程:

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!")
        }
    }
}

为什么我收到错误Smart cast to 'String' is impossible, because 'username' is a mutable property that could have been changed by this time

是否与线程安全无关?

基于null safety docs,我认为这适用于1. usernameprojectName设置在相同的函数中,而不是它们用作params和2.它们作为params的用法包含在if语句中,检查它们的值。

2 个答案:

答案 0 :(得分:7)

Kotlin编译器无法证明usernameprojectName同时被另一个线程变异。私有的领域也没有帮助,因为反思可能绕过这个。

相关文档位于Type Checks and Casts

  

请注意,当编译器无法保证变量在检查和使用之间无法更改时,智能强制转换不起作用。更具体地说,智能演员表适用于以下规则:

     
      
  • val局部变量 - 总是;
  •   
  • val属性 - 如果属性为private或internal,或者在声明属性的同一模块中执行检查。智能转换不适用于具有自定义getter的打开属性或属性;
  •   
  • var局部变量 - 如果在检查和使用之间没有修改变量,并且没有在修改它的lambda中捕获;
  •   
  • var properties - never(因为其他代码可以随时修改变量)。
  •   

改为在局部变量中捕获属性引用。

  

将它们用作params包含在if语句中检查它们的值。

如果Kotlin中的陈述没有“捕获”一个财产。当您声明包含属性的if语句并在块内再次访问它时,编译器可能会为您强制转换它。但是访问规则仍然相同 - getter将被调用两次。

答案 1 :(得分:1)

正如@ mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu指出的那样,Kotlin不会激活var属性的智能投射。我不确定为什么选择这种策略,因为private var可以在预发布版本中智能化,但这就是我们所拥有的。

现在,假设您确定这些属性不会被另一个线程变异,因为它们是private并且没有使用反射。因此if-null检查确保属性不包含空值,但Kotlin编译器不相信。

在这种情况下,我强烈建议使用not-null-assertion运算符!!

presenter.viewReady(this, username!!, projectName!!, buildNumber)

许多人建议避免!!改进您的代码风格,但其含义实际上是"the compiler is dumb, it is obviously not a null, notify me if I am wrong"。像你这样的情况是它首先被引入语言的原因。

还有其他解决方法,包括将属性的当前值保存到本地val(通过帮助函数显式或隐式),但我认为它们在这里不合适,因为它们不表达您的意图。在某些情况下,你需要使用save-current-value-and-work-with-it语义,但是你需要make-the-compiler-trust-the-code语义,它们显然是不一样的。