Умное приведение к типу невозможно, потому что переменная является изменяемым свойством, которое могло быть изменено к этому времени.


275

И новичок из Kotlin спрашивает: «почему не скомпилируется следующий код?»:

    var left: Node? = null

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

Интеллектуальное приведение к «Узлу» невозможно, поскольку «left» является изменяемым свойством, которое могло быть изменено к этому времени.

Я получаю, что leftэто изменяемая переменная, но я явно проверяю left != nullи leftимеет тип, Nodeтак почему он не может быть умно приведен к этому типу?

Как я могу это исправить элегантно? :)


3
Где-то посередине другой поток мог бы снова изменить значение на ноль. Я уверен, что ответы на другие вопросы также упоминают об этом.
— nhaarman

3
Вы можете использовать безопасный вызов, чтобы добавить
— Whymarrh

спасибо @nhaarman, что имеет смысл, Whymarrh, как это можно сделать? Я думал, что безопасные вызовы были только для объектов, а не методов
— FRR

6
Что-то вроде: n.left?.let { queue.add(it) }я думаю?
— Йорн Верни

Ответы:


359

Между выполнением 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. Подумайте о более функциональном решении вашей проблемы, которое не требует изменяемых переменных.
— Спокойной ночи Nerd Pride

1
@sak Это был Nodeкласс, определенный в исходной версии вопроса, в котором n.leftвместо простого кода был более сложный фрагмент кода left. Я обновил ответ соответственно. Спасибо.
— mfulton26

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

1
@ FARID кто имеет в виду?
— mfulton26

3
Да, это безопасно. Когда переменная объявляется как класс global, любой поток может изменить ее значение. Но в случае локальной переменной (переменная, объявленная внутри функции) эта переменная недоступна из других потоков, поэтому безопасна для использования.
— Фарид

31

1) Также вы можете использовать lateinitЕсли вы уверены, что делаете инициализацию позже onCreate()или в другом месте.

Использовать это

lateinit var left: Node

Вместо этого

var left: Node? = null

2) И есть другой способ, который использует !!конец переменной, когда вы используете это так

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

Что это делает?
— С

@ c-an это заставит вашу переменную инициализироваться как null, но ожидайте, что она будет инициализирована позже в коде.
— Радеш

Тогда разве это не то же самое? @Radesh
— c-an

@ с-то же с чем?
— Радеш

1
Я ответил на вышеупомянутый вопрос, что Smart приведение к «Узлу» невозможно, потому что «left» является изменяемым свойством, которое могло быть изменено к этому времени, этот код предотвращает эту ошибку, определяя тип переменной. так что компилятору не нужно умное приведение
— Радеш

27

В ответе mfulton26 есть четвертый вариант.

Используя ?.оператор, можно вызывать как методы, так и поля, не обращаясь к letлокальным переменным и не используя их.

Некоторый код для контекста:

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

Он работает с методами, полями и всем остальным, что я пытался заставить его работать.

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

Для справки, это было проверено в Kotlin 1.1.4-3, но также проверено в 1.1.51и 1.1.60. Там нет гарантии, что он работает на других версиях, это может быть новая функция.

Использование ?.оператора не может быть использовано в вашем случае, поскольку проблема заключается в передаваемой переменной. В качестве альтернативы можно использовать оператор Элвиса, и, вероятно, он требует наименьшего количества кода. Вместо использования, continueхотя, returnтакже может быть использован.

Можно также использовать ручное приведение, но это небезопасно:

queue.add(left as Node);

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


Насколько я понимаю, «?» оператор проверяет, является ли переменная с левой стороны нулевой. В приведенном выше примере это будет «очередь». Ошибка «умное приведение невозможно» относится к параметру «left», передаваемому в метод «add» ... Я все еще получаю ошибку, если использую этот подход
— FRR

Правильно, ошибка включена leftи нет queue. Нужно проверить это, через минуту отредактирую ответ
— Zoe

4

Практическая причина, почему это не работает, не связана с потоками. Дело в том, что node.leftэффективно переводится на node.getLeft().

Это свойство getter может быть определено как:

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 {}

GL


1

Ваше самое элегантное решение должно быть:

var left: Node? = null

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

Тогда вам не нужно определять новую и ненужную локальную переменную, и у вас нет новых утверждений или приведений (которые не являются СУХИМЫМИ). Другие функции области также могут работать, так что выберите ваш любимый.



0

Как бы я написал это:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.