Why does Smartcast not work after nullcheck

2019-04-26 01:39发布

问题:

I'm attempting advent of code and wanted to create a class for day 10. I know that the values can be null, so I declared them as nullable. At some point, I need to check whether the value is assigned and do something with it. There comes the issue. I check beforehand via high != null, but in the line that follows, I have to use !! to convince the compiler that it actually is null.

It seems that it can't find the proper compareTo method, despite nullchecking it first. I guess, it didn't smartcast my variable

private class Bot(val number: Int, var low: Int?, var high: Int?) {

  fun acceptValue(value: Int) {
    if (low == null && high == null) {
      high = value
    } else {
      if (high != null) {
        if (high!! > value) { //it doesn't compile, because appareantly, high is still not considered nonnull at this point
          low = value
        } else {
          low = high
          high = value
        }
      }
    }
  }
}

the kotlin-version is use is 1.1.3-2

Is that a bug? Am I doing something wrong?

回答1:

Between high != null and high > value another thread could do high = null, invalidating the null check. So this is the expected behavior.

The way to solve this is to use a temporary variable which cannot be externally changed:

val cHigh = high
if (cHigh != null) {
    if (cHigh > value) {
        ....


回答2:

Since you defind high as a var, it is mutable. You cannot guarantee the variable is not null even if you have an explicit null check before that.

Official explanation:

Note that smart casts do not work when the compiler cannot guarantee that the variable cannot change between the check and the usage. More specifically, smart casts are applicable according to the following rules:

  • val local variables - always;
  • val properties - if the property is private or internal or the check is performed in the same module where the property is declared. Smart casts aren't applicable to open properties or properties that have custom getters;
  • var local variables - if the variable is not modified between the check and the usage and is not captured in a lambda that modifies it;
  • var properties - never (because the variable can be modified at any time by other code).

In you case, you can use .let:

high?.let {
    if (it > value) {
        low = value
    } else {
      low = it
      high = value
    }
} ?: run {
    //high == null
}

Suggested reading: In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them



回答3:

You can solve this issue if you declare the propertiries private:

private class Bot(val number: Int, 
                  private var low: Int?, 
                  private var high: Int?) {

    ...
}


标签: kotlin