The key point here is that an open
property or a property with a custom getter is not guaranteed to return the same value on successive calls to it.
Therefore the compiler cannot be sure that, once the value received from the property has been checked, it is safe to assume that it will return the same object or even an object of the same type if called again.
Example (quite simplified and synthetic, though):
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
}
This code won't compile, because printArrayList()
expects an ArrayList
and b.value
is open
-- that's what you get in your code. Now, let's make up a derived class that demonstrates what could go wrong:
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
Here it is quite clear that if a property is open
, it can be overridden in a way that successive calls to it return different values. In the example with printArrayList()
there are two such calls. That's why the smart cast would not be safe. The same is true for properties with custom getters.
Your example which performed an as
-cast inside the if
block worked because the cast would fail and throw a ClassCastException
if the property returned a different value of a non-compatible type on the second call, and this would preserve type safety.
And, on contrary, if a val
property is not open
and has a default getter that simply returns the value of the backing field (which is final
in this case), the compiler can safely perform a smart cast: if you get the value of the property several times it is certain to be the same.
An alternative is to get the value once, store it in a local variable and use it several times instead of using the property again:
val list = b.value
if (list is ArrayList) {
printArrayList(list) // smart cast to ArrayList
}
Now, no matter if a property is open
, there is only one call to its getter, and the code then operates with the value the call returned. Since it cannot change, the smart cast is possible here.