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

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 is not pleased with this, citing:

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

我知道一个可能的解决方案是实现以下代码,其中我用<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
    }
}

However, that feels like I'm removing type safety and communication about what the class is used for, and is asking for trouble down the line. Is there a better way to implement this in Kotlin? Additionally - what's the reasoning behind it not being allowed the way I indicated in the first sample? Aren't interfaces just a contract of signatures I'm required to implement, or is there something I'm missing involving generics here?

推荐答案

Yes, you're missing an important detail of generics implementation on JVM: the type erasure. In a nutshell, the compiled bytecode of classes doesn't actually contain any information about generic types (except for some metadata about the fact that a class or a method is generic). All the type checking happens at compile time, and after that no generic types retain in the code, there is just Object.

要发现您 case 中的问题,只需查看字节码(在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()
}

In the bytecode of Converter the generic type is erased:

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

And here are the methods found in the bytecode of 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;
    ...

To inherit the Converter interface, Reverser should in order have a method with the same signature, that is, a type erased one. If the actual implementation method has different signature, a bridge method is added. Here we see that the second method in the bytecode is exactly the bridge method (and it calls the first one).

So, multiple generic interface implementations would trivially clash with each other, because there can be only one bridge method for a certain signature.

Moreover, if it was possible, neither Java nor Kotlin has method overloading based on return value type, and there would also be ambiguity in arguments sometimes, so the multiple inheritance would be quite limited.

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

Kotlin相关问答推荐

Fleet无法从构建文件自动重新加载更改错误'

在Kotlin中处理结果的高阶函数

用浮点数或十进制数给出错误答案的阶乘计算

Kotlin是否针对范围和进度优化sum()?

在intellij中使用kotlin符号和floordiv

"Kotlin中的表达式

使用 Kotlin 的 Springboot 中缺少 ResponseEntity 正文属性

内容更改后的 var 重新计算

如何在 Kotlin 中为变量分配另一个变量的值?

关于 Kotlin 函数类型转换的问题

类型不匹配:Required: Context, Found: Intent

Kotlin suspend fun

Kotlin JVM 和 Kotlin Native 有什么区别?

参数不匹配;SimpleXML

Android EditText 协程go 抖操作符,如 RxJava

如何在伴随对象中使用泛型

函数引用和lambdas

如何在 Kotlin 中按字母顺序对字符串进行排序

如何在 Kotlin 中定义新的运算符?

Kotlin 中的限制函数