Розумний переклад на "Тип" неможливий, оскільки "змінна" - це змінне властивість, яке могло бути змінено до цього часу


275

І новачок у Котліна запитує: "чому не скомпілюється наступний код?":

    var left: Node? = null

    fun show() {
         if (left != null) {
             queue.add(left) // ERROR HERE
         }
    }

Розумний переклад на "Вузол" неможливий, оскільки "зліва" - це змінена властивість, яку можна було змінити до цього часу

Я розумію, що leftце змінна змінна, але я явно перевіряю left != nullі leftмає тип, Nodeтому чому не можна розумно передати цей тип?

Як я можу це виправити елегантно? :)


3
Десь між іншим потоком могло знову змінити значення на нульове. Я майже впевнений, що відповіді на інші запитання також згадують це.
nhaarman

3
Ви можете використовувати безпечний дзвінок, щоб додати
Whymarrh

дякую @ nhaarman, що має сенс, Whymarrh як це можна зробити? Я думав, що безпечні виклики стосуються лише об'єктів, а не методів
FRR

6
Щось на кшталт: n.left?.let { queue.add(it) }я думаю?
Джорн Верні

Відповіді:


357

Між виконання left != nullі queue.add(left)іншим потік міг би змінити значення leftдля null.

Щоб обійти це, у вас є кілька варіантів. Ось декілька:

  1. Використовуйте локальну змінну з інтелектуальним перекладом:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Використовуйте безпечний дзвінок, наприклад один із наступних:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Використовуйте оператор Елвіса з , returnщоб повернутися рано з функції огороджувальної:

    queue.add(left ?: return)

    Зауважте, що breakі continueможе використовуватися аналогічно для перевірок в циклі.


8
4. Подумайте про більш функціональне рішення вашої проблеми, яке не потребує змінних змінних.
Good Night Nerd Pride

1
@sak Це був екземпляр Nodeкласу, визначеного в оригінальній версії питання, який мав складніший фрагмент коду, n.leftа не просто left. Відповідно я оновив відповідь. Дякую.
mfulton26

1
@sak застосовуються ті самі поняття. Ви можете створити нове valдля кожного var, вкласти декілька ?.letвисловлювань або використовувати декілька ?: returnоператорів залежно від функції. напр MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Ви також можете спробувати одне з рішень для "нехай декілька змінних" .
mfulton26

1
@FARID на кого посилаються?
mfulton26

3
Так, це безпечно. Коли змінна оголошується як клас глобальна, будь-який потік може змінювати своє значення. Але у випадку локальної змінної (змінної, оголошеної всередині функції), ця змінна недоступна для інших потоків, тому безпечна у використанні.
Фарид

31

1) Також ви можете використовувати, lateinitякщо ви впевнені, що пізніше onCreate()чи деінде ініціалізуєте .

Використовуй це

lateinit var left: Node

Замість цього

var left: Node? = null

2) І є інший спосіб, коли використовується !!кінець змінної, коли ти її використовуєш так

queue.add(left!!) // add !!

що воно робить?
c-a

@ c-an це зробить вашу змінну ініціалізованою як нульовою, але сподівайтеся, що вона ініціалізується пізніше в коді.
Радеш

Тоді, чи не те саме? @Radesh
c-an

@ c-те саме з чим?
Радеш

1
Я відповів на вище запитання, що Smart cast на "Node" неможливо, тому що "left" - це змінне властивість, яке могло бути змінено до цього часу. Цей код запобігає виникненню помилки за допомогою вказаного типу змінної. тому компілятор не потребує розумної ролі
Radesh

27

На додаток до відповідей mfulton26 є четвертий варіант.

За допомогою ?.оператора можна викликати методи, а також поля, не маючи справу з letмісцевими змінними.

Код контексту:

var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called

Це працює з методами, полями та всіма іншими речами, які я намагався змусити його працювати.

Отже, щоб вирішити проблему, замість того, щоб використовувати ручні касти або використовувати локальні змінні, ви можете використовувати ?.для виклику методів.

Для довідки це було випробувано в Котліні 1.1.4-3, але також випробувано в 1.1.51та 1.1.60. Немає гарантій, що він працює і в інших версіях, це може бути нова функція.

Використання ?.оператора не може бути використане у вашому випадку, оскільки це проблема, яка є переданою змінною. Оператор Елвіса може використовуватися як альтернатива, і, мабуть, саме той вимагає найменшої кількості коду. Замість використання, continueхочаreturn також може бути використаний.

Використання ручного лиття також може бути варіантом, але це не є нульовим безпечним:

queue.add(left as Node);

Значення, якщо ліворуч змінилося на інший потік, програма вийде з ладу.


Наскільки я розумію, "?". Оператор перевіряє, чи є змінною з її лівої сторони нульовою. У наведеному вище прикладі це буде "черга". Помилка "розумного відключення неможлива" посилається на параметр "ліворуч", який передається методу "додати" ... Я все одно отримую помилку, якщо використовую такий підхід
ПНР

Правильно, помилка увімкнена, leftа ні queue. Потрібно перевірити це, відредагуйте відповідь через хвилину
Зої

4

Практична причина, чому це не працює, не пов'язана з потоками. Справа в тому, що node.leftефективно перекладається на node.getLeft().

Цей отримувач властивостей може бути визначений як:

val left get() = if (Math.random() < 0.5) null else leftPtr

Тому два дзвінки можуть не повернути однаковий результат.



1

Зробити це:

var left: Node? = null

fun show() {
     val left = left
     if (left != null) {
         queue.add(left) // safe cast succeeds
     }
}

Здається, це перший варіант, який надає прийнята відповідь, але це те, що ви шукаєте.


Це затінення "лівої" змінної?
AFD

Що абсолютно нормально. Дивіться reddit.com/r/androiddev/comments/fdp2zq/…
EpicPandaForce

1

Щоб існував Smart Cast властивостей, тип даних властивості повинен бути класом, який містить метод або поведінку, до якої ви хочете отримати доступ, а НЕ, щоб властивість була типом суперкласу.


наприклад, на Android

Будь:

class MyVM : ViewModel() {
    fun onClick() {}
}

Рішення:

From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM

Використання:

viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}

ГЛ


1

Вашим найелегантнішим рішенням повинно бути:

var left: Node? = null

fun show() {
    left?.also {
        queue.add( it )
    }
}

Тоді вам не доведеться визначати нову і непотрібну локальну змінну, і у вас немає нових тверджень або анкет (які не бувають DRY). Інші функції сфери можуть також працювати, тому виберіть улюблену.


0

Спробуйте скористатися оператором твердження, що не відповідає нулю ...

queue.add(left!!) 

3
Небезпечний. З тієї ж причини автоматичне кастинг не працює.
Яків Циммерман

3
Це може спричинити збій програми, якщо ліворуч є нульовим.
Pritam Кармакар

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.