I am expecting to see the output
black
white
with below code
package delegate
import kotlinx.coroutines.runBlocking
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
open class Color(private val name: String) {
override fun toString(): String {
return name
}
}
class Black : Color("black")
class White : Color("white")
class ColorCollection {
private val black = Black()
private val white = White()
val list = listOf(black, white)
}
class Palette {
val black: Black by ColorDelegate()
val white: White by ColorDelegate()
val colorCollection = ColorCollection()
}
class ColorDelegate<T> : ReadOnlyProperty<Palette, T> {
override fun getValue(thisRef: Palette, property: KProperty<*>): T {
return thisRef.colorCollection.list.mapNotNull { it as? T }.first()
}
}
fun main() = runBlocking {
val palette = Palette()
println(palette.black)
println(palette.white)
}
However, I only get black output and then Exception in thread "main" java.lang.ClassCastException: delegate.Black cannot be cast to delegate.White.
I found that with this line thisRef.colorCollection.list.mapNotNull { it as? T }, I am expecting it only returns the value in the list that can be safely cast to the generic type, otherwise return null. For example, when accessing black delegated property in Palette, I should only see 1 black element returned by thisRef.colorCollection.list.mapNotNull { it as? T },It actually returns two (black and white). it as? T somehow always works regardless of what T is. I also tried putting a breakpoint at that line, tried "abcdef" as T?, it also works, which I expect to see cast exception that String cannot be cast to Black...


Is this a bug...?
Remember that Type Erasure is a thing in Kotlin, so the runtime does not know what the
Tinit as? T, and hence cannot check the cast for you. Therefore, the cast always succeeds (and something else will fail later down the line). See also this post. IntelliJ should have given you an "unchecked cast" warning here.So rather than checking the type using
T, you can check the type using thepropertyargument:Here, I've checked that the class of the
returnTypeof the property (i.e. the property on which you are putting the delegate) is equal to the list element's runtime class. You can also e.g. be more lenient and checkisSubclassOf.Do note that this wouldn't find any element in the list if the property's type was another type parameter, rather than a class, e.g.
But alas, that's type erasure for you :(