有啥方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)?

Posted

技术标签:

【中文标题】有啥方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)?【英文标题】:Any way to inherit from same generic interface twice (with separate types) in Kotlin?有什么方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)? 【发布时间】:2016-02-20 19:53:51 【问题描述】:

我的代码中有一个场景,我希望一个类为两种不同的类型实现一个接口,比如这个例子:

interface Speaker<T> 
    fun talk(value: T)


class Multilinguist : Speaker<String>, Speaker<Float> 
    override fun talk(value: String) 
        println("greetings")
    

    override fun talk(value: Float) 
        // Do something fun like transmit it along a serial port
    

Kotlin 对此并不满意,引用:

Type parameter T of 'Speaker' has inconsistent values: kotlin.String, kotlin.Float
A supertype appears twice

我知道一种可能的解决方案是实现以下代码,其中我使用&lt;Any&gt; 实现接口,然后自己检查类型并将它们委托给它们的函数。

interface Speaker<T> 
    fun talk(value: T)


class Multilinguist : Speaker<Any> 
    override fun talk(value: Any) 
        when (value) 
            is String ->
                internalTalk(value)
            is Float ->
                internalTalk(value)
         
    

    fun internalTalk(value: String) 
        println(value)
    

    fun internalTalk(value: Float) 
        // Do something fun like transmit it along a serial port
    

但是,这感觉就像我正在删除类型安全性和关于该类的用途的交流,并且正在自找麻烦。有没有更好的方法在 Kotlin 中实现这一点?另外 - 我在第一个示例中指出的方式不允许它背后的原因是什么?接口不只是我需要实现的签名合同,还是我在这里缺少涉及泛型的东西?

【问题讨论】:

【参考方案1】:

是的,您错过了 JVM 上泛型实现的一个重要细节:the type erasure。简而言之,类的编译字节码实际上并不包含任何关于泛型类型的信息(除了一些关于类或方法是泛型这一事实的元数据)。所有类型检查都发生在编译时,之后代码中不再保留泛型类型,只有Object

要发现您的问题,只需查看字节码(在 IDEA、Tools -&gt; Kotlin -&gt; Show Kotlin Bytecode 或任何其他工具中)。让我们考虑这个简单的例子:

interface Converter<T> 
    fun convert(t: T): T


class Reverser(): Converter<String> 
    override fun convert(t: String) = t.reversed()

Converter 的字节码中,泛型类型被擦除:

// access flags 0x401
// signature (TT;)TT;
// declaration: T convert(T)
public abstract convert(Ljava/lang/Object;)Ljava/lang/Object;

下面是在Reverser的字节码中找到的方法:

// access flags 0x1
public convert(Ljava/lang/String;)Ljava/lang/String;
    ...

// access flags 0x1041
public synthetic bridge convert(Ljava/lang/Object;)Ljava/lang/Object;
    ...
    INVOKEVIRTUAL Reverser.convert (Ljava/lang/String;)Ljava/lang/String;
    ...

要继承Converter 接口,Reverser 应该有一个具有相同签名的方法,即类型已擦除的方法。如果实际实现方法有不同的签名,则添加bridge method。在这里我们看到字节码中的第二个方法正是桥接方法(它调用了第一个)。

因此,多个通用接口实现会相互冲突,因为对于某个签名只能有一个桥接方法。

而且如果可以的话,Java和Kotlin都不是has method overloading based on return value type,而且有时参数也会有歧义,所以多重继承会受到很大限制。

不过,Project Valhalla 会有所改变(具体化的泛型将在运行时保留实际类型),但我仍然不希望有多个泛型接口继承。

【讨论】:

以上是关于有啥方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)?的主要内容,如果未能解决你的问题,请参考以下文章

有啥方法可以让我在一个 pyspark 脚本中从 10 个不同的模式中提取数据?

通用扩展类 AND 在 Kotlin 中实现接口

Kotlin 1.5 新特性:密封接口有啥用?

有啥方法可以在 php 的返回页面中从 PayPal 获取交易详细信息?

Android中BaseColumns有啥用

从 Kotlin 实现 Java 接口时出现 NullPointerException