具有通用类型值的 Kotlin 委托的 ReadOnlyProperty 在 getValue 中未正确转换
Kotlin delegate's ReadOnlyProperty with generic type value dose not cast correctly in getValue
我期待看到输出
black
white
使用以下代码
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)
}
然而,我只得到黑色输出然后Exception in thread "main" java.lang.ClassCastException: delegate.Black cannot be cast to delegate.White
。
我发现对于这一行 thisRef.colorCollection.list.mapNotNull { it as? T }
,我只希望它是列表中可以安全地转换为泛型类型的值 return,否则 return 为空。例如,当访问 Palette 中的黑色委托 属性 时,我应该只看到由 thisRef.colorCollection.list.mapNotNull { it as? T }
编辑的 1 个黑色元素 return,它实际上是 return 两个(黑色和白色)。 it as? T
无论 T 是什么,总能以某种方式起作用。我还尝试在该行放置一个断点,尝试将“abcdef”作为 T?,它也有效,我希望看到 String 无法转换为 Black 的转换异常...
这是一个错误吗...?
这是由于泛型的类型擦除。转换为泛型总是会成功,因为泛型在运行时是未知的。如果将值分配给具体不匹配类型的变量或调用它不具有的函数,这可能会在其他地方导致运行时异常。
由于转换为通用类型是危险的(它默默地工作,允许在代码中的不同位置出现错误),所以当您像在代码中那样执行时会出现编译器警告。警告说“未经检查的强制转换”,因为强制转换是在没有类型检查的情况下发生的。当您转换为具体类型时,运行时会检查转换站点的类型,如果不匹配,它会立即抛出 ClassCastException,或者在安全转换 as?
.[=12= 的情况下解析为 null ]
记住Type Erasure is a thing in Kotlin, so the runtime does not know what the T
in it 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 应该在这里给你一个“未经检查的转换”警告。
因此,与其使用 T
检查类型,不如使用 property
参数检查类型:
class ColorDelegate<T> {
operator fun getValue(thisRef: Palette, property: KProperty<*>) =
// assuming such an item always exists
thisRef.colorCollection.list.first {
property.returnType.classifier == it::class
} as T
}
fun main() {
val palette = Palette()
println(palette.black) // prints "black"
println(palette.white) // prints "white"
}
在这里,我检查了 属性 的 returnType
的 class(即您放置代表的 属性)是否相等到列表元素的运行时 class。你也可以,例如宽大点,检查 isSubclassOf
.
请注意,如果 属性 的类型是另一个类型参数,而不是 class,这将不会在列表中找到任何元素,例如
class Palette<T> {
...
val foo: T by ColorDelegate()
...
}
但是,唉,这对你来说就是类型擦除:(
我期待看到输出
black
white
使用以下代码
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)
}
然而,我只得到黑色输出然后Exception in thread "main" java.lang.ClassCastException: delegate.Black cannot be cast to delegate.White
。
我发现对于这一行 thisRef.colorCollection.list.mapNotNull { it as? T }
,我只希望它是列表中可以安全地转换为泛型类型的值 return,否则 return 为空。例如,当访问 Palette 中的黑色委托 属性 时,我应该只看到由 thisRef.colorCollection.list.mapNotNull { it as? T }
编辑的 1 个黑色元素 return,它实际上是 return 两个(黑色和白色)。 it as? T
无论 T 是什么,总能以某种方式起作用。我还尝试在该行放置一个断点,尝试将“abcdef”作为 T?,它也有效,我希望看到 String 无法转换为 Black 的转换异常...
这是一个错误吗...?
这是由于泛型的类型擦除。转换为泛型总是会成功,因为泛型在运行时是未知的。如果将值分配给具体不匹配类型的变量或调用它不具有的函数,这可能会在其他地方导致运行时异常。
由于转换为通用类型是危险的(它默默地工作,允许在代码中的不同位置出现错误),所以当您像在代码中那样执行时会出现编译器警告。警告说“未经检查的强制转换”,因为强制转换是在没有类型检查的情况下发生的。当您转换为具体类型时,运行时会检查转换站点的类型,如果不匹配,它会立即抛出 ClassCastException,或者在安全转换 as?
.[=12= 的情况下解析为 null ]
记住Type Erasure is a thing in Kotlin, so the runtime does not know what the T
in it 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 应该在这里给你一个“未经检查的转换”警告。
因此,与其使用 T
检查类型,不如使用 property
参数检查类型:
class ColorDelegate<T> {
operator fun getValue(thisRef: Palette, property: KProperty<*>) =
// assuming such an item always exists
thisRef.colorCollection.list.first {
property.returnType.classifier == it::class
} as T
}
fun main() {
val palette = Palette()
println(palette.black) // prints "black"
println(palette.white) // prints "white"
}
在这里,我检查了 属性 的 returnType
的 class(即您放置代表的 属性)是否相等到列表元素的运行时 class。你也可以,例如宽大点,检查 isSubclassOf
.
请注意,如果 属性 的类型是另一个类型参数,而不是 class,这将不会在列表中找到任何元素,例如
class Palette<T> {
...
val foo: T by ColorDelegate()
...
}
但是,唉,这对你来说就是类型擦除:(