我正在学习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
是一个具有开放或自定义吸气功能的产品。
我想也许错误是因为活动可以为空,所以我尝试了:
if(activity!=null && activity is ScreenRouter) {
decoupler.attachRouter(activity)
}
但它没有用,编译失败并出现同样的错误。
但是,以下代码可以正常工作:
if(activity is ScreenRouter) {
decoupler.attachRouter(activity as ScreenRouter)
}
没关系但是上面的错误似乎没有解释为什么smartcast失败了。我不是Kotlin专家,我只是一个学习Kotlin的初学者。我找不到任何文件。这些错误描述使Kotlin难以学习。任何人都可以用简单的语言解释一下吗?
答案 0 :(得分:26)
这里的关键点是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的属性也是如此。
您在as
块中执行if
- 演员的示例有效,因为如果属性返回不兼容类型的不同值,则强制转换将失败并抛出ClassCastException
在第二次通话时,这将保持类型安全。
相反,如果val
属性不是open
并且有一个默认的getter,它只返回backing field的值final
case),编译器可以安全地执行智能转换:如果你多次获得属性的值,它肯定是相同的。
另一种方法是获取值一次,将其存储在局部变量中并多次使用它,而不是再次使用该属性:
val list = b.value
if (list is ArrayList) {
printArrayList(list) // smart cast to ArrayList
}
现在,无论属性是open
,只有一次调用它的getter,然后代码将使用调用返回的值进行操作。由于它无法改变,因此可以在这里进行智能演员。
答案 1 :(得分:2)
我不是直接使用可为空的对象的活动,而是这样做了
activity?.let{
if(it is ScreenRouter) {
decoupler.attachRouter(it)
}
}