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


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.