智能广播是不可能的,因为属性具有开放或自定义的 getter

2022-08-31 22:32:40

我正在学习 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
}

文档中所写,kotlin 在检查 is 运算符后自动转换为类型。所以,我期望它会起作用。但相反,它困扰着我编译错误说:

智能投射到 ScreenRouter 是不可能的,因为活动是具有打开或自定义 getter 的属性。

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

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

但它不起作用,编译失败,出现相同的错误。

但是,以下代码工作正常:

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

没关系,但上面的错误似乎并不能解释为什么智能广播会失败。我不是 Kotlin 专家,我只是一个学习 Kotlin 的初学者。我在任何地方都没有找到任何文档。这些错误描述使 Kotlin 学习起来很糟糕。任何人都可以用简单的术语解释吗?


答案 1

这里的关键点是,属性或具有自定义 getter 的属性不能保证在连续调用时返回相同的值。open

因此,编译器无法确定,一旦检查了从属性接收的值,就可以安全地假定,如果再次调用,它将返回相同的对象,甚至是相同类型的对象。

示例(虽然非常简化和合成):

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
}

这段代码不会编译,因为期望一个 and 是 - 这就是你在代码中得到的。现在,让我们组成一个派生类,演示可能出错的地方:printArrayList()ArrayListb.valueopen

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

这里很清楚,如果一个属性是 ,它可以被覆盖,连续调用它返回不同的值。在示例中,有两个这样的调用。这就是为什么聪明的演员不会安全。对于具有自定义 getter 的属性也是如此。openprintArrayList()

在块内执行 -cast 的示例之所以有效,是因为如果属性在第二次调用中返回了不兼容类型的不同值,则强制转换将失败并引发 a,这将保持类型安全性。asifClassCastException

相反,如果属性不是,并且具有仅返回支持字段值(在本例中为)的默认 getter,则编译器可以安全地执行智能强制转换:如果您多次获取该属性的值,则它肯定是相同的。valopenfinal


另一种方法是获取一次值,将其存储在局部变量中并多次使用它,而不是再次使用该属性:

val list = b.value

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

现在,无论属性是否为 ,都只有一个对其 getter 的调用,然后代码使用调用返回的值进行操作。由于它无法改变,因此在这里可以进行智能铸造。open


答案 2

而不是直接使用活动,这是一个可为空的对象,我这样做是有效的

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