Почему интеллектуальное сканирование сбрасывается для значения NULL после установки значения и проверки, является ли оно нулевым?

Допустим, у меня есть следующий класс:

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 ?

Это связано с тем, что вы не потокобезопасны?

Основываясь на нулевых документах по безопасности , я думал, что это будет работать для одного из 1., что username и projectName установлены в одной и той же функции выше их использования в качестве параметров и 2. что их использование в качестве параметров завернуто в оператор if, проверяющий их значение ,

Компилятор Kotlin не может доказать, что либо username либо projectName мутируются другим потоком одновременно. Поле, являющееся частным, также не помогает, поскольку отражение может обойти это.

Соответствующая документация для этого относится к типу проверки и броскам :

Обратите внимание: умные приведения не работают, когда компилятор не может гарантировать, что переменная не может измениться между проверкой и использованием. Более конкретно, интеллектуальные отладки применимы в соответствии со следующими правилами:

  • val локальные переменные – всегда;
  • свойства val – если свойство является частным или внутренним, или проверка выполняется в том же модуле, где объявлено свойство. Смарт-броски неприменимы к открытым свойствам или свойствам, которые имеют пользовательские геттеры;
  • var local variables – если переменная не изменяется между проверкой и использованием и не записывается в лямбда, которая ее модифицирует;
  • var properties – never (потому что переменная может быть изменена в любой момент другим кодом).

Вместо этого возьмите ссылку на свойство в локальной переменной.

что их использование в качестве параметров завернуто в оператор if, проверяющий их значение.

if-заявления в Котлине не «захватывают» собственность. Когда вы объявляете if-statement, включающее свойство, и возвращаете его снова внутри блока, компилятор может использовать его для вас. Но правила для доступа все те же: геттер будет вызываться дважды.

Как отметил @ mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu, Kotlin не активирует интеллектуальное кастинг для свойств var . Я не совсем уверен, почему эта стратегия была выбрана, так как private var можно было использовать в предварительных версиях, но это то, что у нас есть.

Теперь предположим, что вы уверены, что эти свойства не мутируются другим потоком, поскольку они являются private и не используется. Таким образом, проверка if-null гарантирует, что свойства не содержат нулей, но компилятор Kotlin не считает это.

В такой ситуации я настоятельно рекомендую использовать оператор не-нуль-утверждения !! :

 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, и они явно не совпадают.