android 无法进行Smartcast,因为属性具有开放或自定义getter

polhcujo  于 2022-11-27  发布在  Android
关注(0)|答案(3)|浏览(145)

我正在学习Kotlin。我的代码如下:

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    decoupler.attachNotifier(this)
    if(activity is ScreenRouter) {
        decoupler.attachRouter(activity)
    }
}

attachRouter()方法:

fun attachRouter(router: ScreenRouter?) {
    this.router = router
}

正如在documentation中所写的,kotlin在检查is运算符后自动转换为类型。所以,我期望它能工作。但相反,它用编译错误来困扰我,说:

    • 无法Smartcast到ScreenRouter,因为activity是具有开放或自定义getter的属性。**

我想错误可能是因为活动可以为空,所以我尝试:

if(activity!=null && activity is ScreenRouter) {
     decoupler.attachRouter(activity)
}

但它不起作用,编译失败,出现同样的错误。
但是,以下代码可以正常工作:

if(activity is ScreenRouter) {
    decoupler.attachRouter(activity as ScreenRouter)
}

没关系,但是上面的错误似乎没有解释任何关于smartcast失败的原因。我不是KotlinMaven,我只是一个学习Kotlin的初学者。我在任何地方都没有找到任何文档。这种错误描述让Kotlin很难学习。有人能用简单的语言解释一下吗?

cyvaqqii

cyvaqqii1#

这里的关键点是,不能保证open属性或具有自定义getter的属性在连续调用时返回相同的值。
因此,编译器无法确定,一旦检查从属性接收的值,就可以放心地假设,如果再次呼叫,它会传回相同的对象,什至是相同型别的对象。
示例(虽然非常简化和综合):

open class Base {
    open val value: List<Int> = ArrayList()
}

val b : Base = foo()

fun printArrayList(list: ArrayList<Int>) { /* ... */ }

if (b.value is ArrayList) { // first call
    printArrayList(b.value) // second call, smart cast is impossible
}

这段代码不会编译,因为printArrayList()需要一个ArrayList,而b.value实际上是open--这就是你在代码中得到的结果。现在,让我们构造一个派生类来演示什么可能出错:

class Derived : Base() {
    private var counter = 0

    override val value: List<Int>
        get() {
            ++counter
            return if (counter % 2 == 0)
                ArrayList() else
                LinkedList()
        }
}

val b = Derived()
println(b.value.javaClass) // class java.util.LinkedList
println(b.value.javaClass) // class java.util.ArrayList

这里很明显,如果一个属性是open,它可以被重写,使得对它的连续调用返回不同的值。在printArrayList()的例子中,有两个这样的调用。这就是为什么智能转换不安全的原因。对于带有自定义getter的属性也是如此。
您在if区块内执行as转换的范例可以运作,因为如果属性在第二次呼叫时传回不相容型别的不同值,转换就会失败并掷回ClassCastException,而这会保留型别安全。
相反,如果val属性不是open,并且有一个默认的getter只返回支持字段的值(在本例中为final),则编译器可以安全地执行智能强制转换:如果多次获取该属性的值,则它肯定是相同的。
另一种方法是取得值一次,将它储存在局部变量中,然后多次使用它,而不是再次使用属性:

val list = b.value

if (list is ArrayList) {
    printArrayList(list) // smart cast to ArrayList
}

现在,无论属性是否为open,都只有一次对其getter的调用,然后代码使用调用返回的值进行操作。因为它不能更改,所以这里可以进行智能转换。

e5nqia27

e5nqia272#

我没有直接使用activity,因为它是一个可以为空的对象,我这样做是有效的

activity?.let{
   if(it is ScreenRouter) {
      decoupler.attachRouter(it)
   }
}
falq053o

falq053o3#

与此代码段没有直接关系,但一种常见的情况是在ViewModel中定义了一个state,如下所示:

private val _state = MutableStateFlow<CardScreenState>(CardScreenState.Loading)
    val state: StateFlow<CardScreenState> = _state

然后,当您在when子句中使用此state

when (state) {

您将得到相同的错误。
在这里,作为解决方案,您可以使用不可变的getter创建一个状态变量,如下所示:

when (val screenState = state) {

相关问题