【问题标题】:Kotlin delegate's ReadOnlyProperty with generic type value dose not cast correctly in getValueKotlin 委托的具有泛型类型值的 ReadOnlyProperty 在 getValue 中未正确转换
【发布时间】:2021-10-23 13:41:23
【问题描述】:

我期待看到输出

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 },我希望它只返回列表中可以安全地转换为泛型类型的值,否则返回null。例如,在 Palette 中访问黑色委托属性时,我应该只看到 thisRef.colorCollection.list.mapNotNull { it as? T } 返回的 1 个黑色元素,它实际上返回了两个(黑色和白色)。无论 T 是什么,it as? T 总是以某种方式工作。我还尝试在该行放置一个断点,尝试将“abcdef”设置为 T?,它也可以工作,我希望看到 String 无法转换为 Black 的转换异常......

这是一个错误吗...?

【问题讨论】:

    标签: kotlin kotlin-delegate


    【解决方案1】:

    请记住 Type Erasure 是 Kotlin 中的一个东西,因此运行时不知道 it as? T 中的 T 是什么,因此无法为您检查演员表。因此,演员表总是成功的(以后会有其他事情失败)。另见this post。 IntelliJ 应该在这里给你一个“未经检查的演员表”警告。

    因此,您可以使用property 参数检查类型,而不是使用T 检查类型:

    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 的类(即您放置委托的属性)是否等于列表元素的运行时类。你也可以例如更宽容,检查isSubclassOf

    请注意,如果属性的类型是另一个类型参数,而不是一个类,这不会在列表中找到任何元素,例如

    class Palette<T> {
        ...
        val foo: T by ColorDelegate()
        ...
    }
    

    但是,唉,这对你来说是类型擦除:(

    【讨论】:

    • property.returnType.classifier == it::class。谢谢,这对我有用!
    【解决方案2】:

    这是由于使用泛型进行类型擦除所致。转换为泛型类型总是成功的,因为泛型类型在运行时是未知的。如果将值分配给具体不匹配类型的变量或在其上调用它没有的函数,这可能会导致其他地方出现运行时异常。

    由于转换为泛型类型是危险的(它默默地工作,允许在代码中的不同位置发生错误),当您像在代码中那样执行此操作时会出现编译器警告。警告说“未经检查的演员表”,因为演员表在没有类型检查的情况下发生。当您转换为具体类型时,运行时会在转换站点检查类型,如果存在不匹配,它会立即抛出 ClassCastException,或者在安全转换 as? 的情况下解析为 null。

    Info about type erasure in the Kotlin documentation

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-24
      • 2018-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多