无法将类型强制转换为“类型”,因为“变量”是可变属性,到那时可能已经更改了


275

然后Kotlin新手问,“为什么下面的代码不编译?”:

    var left: Node? = null

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

无法智能地将其强制转换为“节点”,因为“左”是一个可变属性,可能此时已更改

我得到的left是可变变量,但是我正在显式检查left != null并且left是类型,Node所以为什么不能将其智能转换为该类型?

我该如何解决?:)


3
在不同线程之间的某个地方,该值可能再次更改为null。我很确定其他问题的答案也提到了这一点。
nhaarman

3
您可以使用一个安全的电话进行添加
-Whymarrh

感谢@nhaarman,这是有道理的,为什么要怎么做?我以为安全调用只针对对象而不是方法
FRR

6
有点像:n.left?.let { queue.add(it) }我想吗?
乔恩·弗妮

Answers:


357

在执行left != null和之间,queue.add(left)另一个线程可能将的值更改leftnull

要解决此问题,您有几种选择。这里有一些:

  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. 使用Elvis运算符return可以从封闭函数中提前返回:

    queue.add(left ?: return)

    请注意,break并且continue可以类似地用于循环内的检查。


8
4.考虑一种不需要可变变量的功能更强大的解决方案。
晚安书呆子骄傲

1
@sak这是Node问题的原始版本中定义的类的实例,该类具有更复杂的代码段,n.left而不是简单的left。我已经相应地更新了答案。谢谢。
mfulton18年

1
@sak适用相同的概念。您可以val为每个新建一个var,嵌套多个?.let语句,或?: return根据功能使用多个语句。例如MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return)。您也可以尝试“多变量let”的一种解决方案。
mfulton26 '18

1
@FARID指的是谁?
mfulton19

3
是的,很安全。将变量声明为全局类时,任何线程都可以修改其值。但是在使用局部变量(在函数内部声明的变量)的情况下,该变量无法从其他线程访问,因此使用起来很安全。
Farid

31

1)您也可以使用lateinit如果您确定稍后在onCreate()其他地方进行初始化。

用这个

lateinit var left: Node

代替这个

var left: Node? = null

2)还有其他这样使用!!变量结尾的方式

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

它有什么作用?
c-

@ c-an会使您的变量初始化为null,但希望确保稍后在代码中进行初始化。
Radesh

那不一样吗 @Radesh
C-的

@ c-an与什么相同?
Radesh

1
我回答了以上问题,无法将Smart强制转换为“ 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

它可以与方法,字段以及我试图使其正常工作的所有其他东西一起使用。

因此,为了解决此问题,您可以使用?.调用方法,而不必使用手动强制转换或使用局部变量。

作为参考,它在Kotlin中进行了测试1.1.4-3,但也在1.1.51和中进行了测试1.1.60。无法保证它可以在其他版本上使用,这可能是一项新功能。

?.在您的情况下,不能使用运算符,因为这是传递的变量。Elvis运算符可以替代使用,它可能是需要最少代码量的运算符。除了使用之外continuereturn还可以使用。

也可以选择使用手动强制转换,但这不是null安全的方法:

queue.add(left as Node);

这意味着如果在其他线程上更改了left ,程序将崩溃。


据我了解,“?”。操作员正在检查其左侧的变量是否为null。在上面的示例中为“ queue”。错误“无法进行智能转换”是指将参数“ left”传递到方法“ add”中……如果使用这种方法,我仍然会收到错误
FRR

是的,错误在,left而不是在queue。需要检查一下,将在一分钟内编辑答案
Zoe

4

这样做不起作用的实际原因与线程无关。关键是要node.left有效地翻译成node.getLeft()

此属性获取器可以定义为:

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

因此,两个调用可能不会返回相同的结果。




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

然后,您不必定义新的不必要的局部变量,也不需要任何新的断言或强制转换(不是DRY)。其他示波器功能也可以使用,因此请选择您喜欢的。



0

我怎么写:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.