【问题标题】:Kotlin compiler cannot verify generic type of classKotlin 编译器无法验证类的泛型类型
【发布时间】:2020-03-12 10:15:34
【问题描述】:

TL;DR

Kotlin 编译器在此处给出错误(类型不匹配):

fun <T: A()> getUtil(t: T): Util<T> = if (t is B) UtilB() else // ...

B 类的签名为:class B : A()Util 类为class Util&lt;T: A&gt;UtilB 类为class UtilB: Util&lt;B&gt;()

Kotlin 编译器在此处给出警告(未经检查的强制转换):

fun <T: A()> getUtil(t: T): Util<T> = if (t is B) UtilB() as Util<T> else // ...

据我了解,Kotlin 智能演员应该知道 UtilB() as Util&lt;T&gt;t is B 检查。

Java 代码和编译器给出完全相同的结果。

据我所知,这一定是对 Java 泛型的限制。我该如何解决这个问题?

问题描述

我有以下设置,其中一个抽象类有多个实现,而一个 util 类为每个实现提供相同的功能。

为了类型安全,我想我会创建一个抽象类Util&lt;T: A&gt;,并为A 的每个派生类创建另一个UtilB: Util&lt;B&gt; 类。

为了为每个实现获取正确的 util 类,我在伴随对象 getUtil 上创建了一个函数,它基于泛型类型 T 的参数为每个实现返回正确的 util 类,该参数扩展了 A:T: A 从而具有返回类型Util&lt;T&gt;

但是,当我为A 的每个派生类编写函数体时,使用is B 检查参数的类型,然后使用UtilB() 返回正确的实用程序,Kotlin 编译器在返回点说 UtilB 不是 Util&lt;T&gt; 类型,即使它应该是。

然后我将 UtilB 转换为 Util&lt;B&gt; 并且有效,但给了我一个错误“未经检查的演员”。根据我的理解,Kotlin 智能转换应该能够确定它确实是一个有效的检查转换(使用is B 检查),并且在运行快速测试后证明它也是有效的......

我用 Java 重写了相同的代码,结果完全相同...

据我所知,这是 Java/Kotlin 泛型的限制。 我想知道如何检查这个演员表。有可能吗?

代码

这是一个最小的工作(或不工作)示例:

abstract class A
class B : A()
class C : A()

abstract class Util<T : A> {
    abstract fun getName(): String
    companion object {
        fun <T : A> getUtil(t: T): Util<T> = when(t) {
            is B -> UtilB() as Util<T> // warning
            is C -> UtilC() // this event gives an error
            else -> throw IllegalArgumentException("No util for this class.")
        }
    }
}

class UtilB : Util<B>() {
    override fun getName(): String = "B"
}

class UtilC : Util<C>() {
    override fun getName(): String = "C"
}

fun main() {
    val b = B()
    val c = C()
    val utilB = Util.getUtil(b)
    val utilC = Util.getUtil(c)
    println(utilB.getName()) // prints B
    println(utilC.getName()) // prints C
}

【问题讨论】:

    标签: generics kotlin


    【解决方案1】:

    这是修改后的版本:

    abstract class A
    class B : A()
    class C : A()
    
    abstract class Util<T : A> {
        abstract fun getName(): String
        companion object {
            fun <T : A> getUtil(t: T): Util<A> = when(t) {    // here return Util<A>
                is B -> UtilB()                               // No need cast
                is C -> UtilC()
                else -> throw IllegalArgumentException("No util for this class.")
            }
        }
    }
    
    class UtilB : Util<A>() {                   // Replace B by A
        override fun getName(): String = "B"
    }
    
    class UtilC : Util<A>() {                  // Replace C by A
        override fun getName(): String = "C"
    }
    
    fun main() {
        val b = B()
        val c = C()
        val utilB = Util.getUtil(b)
        val utilC = Util.getUtil(c)
        println(utilB.getName()) // prints B
        println(utilC.getName()) // prints C
    }
    

    但我不确定你的方法是否正确。 当我看到这种模式时,我看到了密封类。

    这是一个使用密封类的实现:

    sealed class AA {
        class BB : AA()
        class CC : AA()
    }
    sealed class AAU {
        abstract fun getName():String
    
        class BBU : AAU() {
            override fun getName()= "BB"
        }
    
        class CCU : AAU(){
            override fun getName()= "CC"
        }
    }
    
    fun getU(aa: AA) =
        when(aa) {
            is AA.BB -> AAU.BBU()
            is AA.CC -> AAU.CCU()
        }
    
    fun main() {
       val bb = AA.BB()
       val cc = AA.CC()
       val bbu = getU(bb)
       val ccu = getU(cc)
       println(bbu.getName())
       println(ccu.getName())
    }
    

    所以这里不需要抛出异常。

    还可以更简单:

    sealed class AA {
        class BB : AA()
        class CC : AA()
    }
    
    fun getName(aa: AA) =
            when(aa) {
                is AA.BB -> "BB"
                is AA.CC -> "CC"
            }
    
    fun main() {
        val bb = AA.BB()
        val cc = AA.CC()
        println(getName(bb))
        println(getName(cc))
    }
    

    由于我不知道您的上下文,最后一个实现可能不是正确的方法。

    【讨论】:

    • 在您的第一个版本中,我失去了 UtilB 的类型安全性,因为它不再是一个仅适用于 B 的 util 类,而是一般适用于 A。例如。 val util: Util&lt;B&gt; = UtilB() 将不再起作用。
    • 我不了解密封类,将对其进行研究。它们可能有问题,因为我的 A、B 和 C 类实际上是 JPA 实体......至于你最后一个例子:正确,这不起作用,因为 Util 上有更多方法,并且它们在实现上有很大差异。感谢您的快速回复,我将研究密封类或您可能有的任何其他额外提示:)
    • @Wernerson 好的,我更了解您的问题。在您的情况下,您没有类型安全性,因为您手动将 B 或 C 映射到 UtilB 或 UtilC。你有能力修改 A、B 和 C 类吗?
    • 在一定限度内是的。 A 类是一个抽象类,BC 是它的派生类。它们都是 JPA 实体。
    【解决方案2】:

    据我了解,Kotlin 智能转换应该知道 UtilB() as Util&lt;T&gt;t is B 检查。

    不应该,因为它是错误的。打电话是完全合法的

    getUtil<A>(B())
    

    在这种情况下t is B 为真,但TA 并且UtilB 不扩展Util&lt;A&gt;。您可以尝试使用协方差来修复它,但这也不起作用:

    class D : B()
    
    getUtil<D>(D())
    

    现在UtilB 也必须扩展Util&lt;D&gt;

    一种可能的解决方案是将F-bounded type parameter 添加到A,但该方法仍然必须是成员以避免强制转换(Kotlin 不会像 Scala 那样在 when 分支内细化类型参数):

    abstract class A<T : A<T>> {
        abstract fun util(): Util<T>
    }
    class B : A<B>() {
        override fun util() = UtilB()
    }
    class C : A<C>() {
        override fun util() = UtilC()
    }
    
    abstract class Util<T : A<T>> {
        abstract fun getName(): String
    }
    
    class UtilB : Util<B>() {
        override fun getName(): String = "B"
    }
    
    class UtilC : Util<C>() {
        override fun getName(): String = "C"
    }
    

    【讨论】:

    • 是的,但是 B 扩展了 A 所以第一个应该可以工作,不是吗?
    • 您的第二个论点是有效的,但您的回答并不能解决我的问题。您对此有解决方案吗?
    • 不,B 扩展 A 并不意味着 Util&lt;B&gt; 扩展 Util&lt;A&gt;。请阅读方差:kotlinlang.org/docs/reference/generics.html#variance.
    • 如果限制可以接受,请查看编辑以获取可能的修复。如果不是,我相信你必须忍受演员表(但你仍然应该使用A&lt;T : A&lt;T&gt;&gt; 来使演员表真正正确)。
    猜你喜欢
    • 2017-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-05
    • 1970-01-01
    • 2021-08-22
    • 2017-12-30
    • 2016-03-24
    相关资源
    最近更新 更多