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

275

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

    var left: Node? = null

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

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

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

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

FRR
источник
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может использоваться аналогично для проверок внутри циклов.

mfulton26
источник
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 !!
Radesh
источник
Что это делает?
С
@ 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);

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

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

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

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

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

Поэтому два вызова могут не возвращать один и тот же результат.

Роланд Иллиг
источник
2

Изменить var left: Node? = nullна lateinit var left: Node. Задача решена.

Мухаммед Мансур
источник
1

Сделай это:

var left: Node? = null

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

Который, кажется, является первым вариантом, предоставленным принятым ответом, но это то, что вы ищете.

EpicPandaForce
источник
Это слежка за "левой" переменной?
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

Попробуйте использовать оператор утверждения not-null ...

queue.add(left!!) 
Bikeboy
источник
3
Dangerous. По той же причине авто-кастинг не работает.
Джейкоб Циммерман
3
Это может вызвать сбой приложения, если оставлено нулевым.
Притам Кармакар
0

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

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
tonisives
источник