在Kotlin中以任何方式从相同的通用接口继承两次(使用不同的类型)?

Mer*_*gic 21 generics interface multiple-inheritance kotlin

我在我的代码中有一个场景,我想要一个类来实现两个不同类型的接口,比如这个例子:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

Kotlin对此不满意,引用:

Type parameter T of 'Speaker' has inconsistent values: kotlin.String, kotlin.Float
A supertype appears twice
Run Code Online (Sandbox Code Playgroud)

我知道一个可能的解决方案是实现以下代码,我在其中实现接口,<Any>然后自己检查类型并将它们委托给它们的函数.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,这感觉就像我正在删除类型安全和关于类的用途的沟通,并且正在寻找麻烦.有没有更好的方法在Kotlin实现这个?另外 - 它背后的原因是什么,不允许我在第一个样本中指出的方式?接口不仅仅是我需要实现的签名合同,还是我在这里缺少涉及泛型的东西?

hot*_*key 20

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

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

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

class Reverser(): Converter<String> {
    override fun convert(t: String) = t.reversed()
}
Run Code Online (Sandbox Code Playgroud)

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

// access flags 0x401
// signature (TT;)TT;
// declaration: T convert(T)
public abstract convert(Ljava/lang/Object;)Ljava/lang/Object;
Run Code Online (Sandbox Code Playgroud)

以下是字节码中的方法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;
    ...
Run Code Online (Sandbox Code Playgroud)

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

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

而且,如果可能的话,Java和Kotlin 没有基于返回值类型的方法重载,并且有时参数也会有歧义,因此多重继承将非常有限.

然而,事情会随着Project Valhalla而改变(reified generics将在运行时保留实际类型),但我仍然不希望有多个通用接口继承.