有什么方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)?
Any way to inherit from same generic interface twice (with separate types) in Kotlin?
我的代码中有一个场景,我希望 class 为两种不同的类型实现一个接口,例如这个例子:
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
我知道一种可能的解决方案是实现以下代码,其中我使用 <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
}
}
然而,这感觉就像我正在删除类型安全和关于 class 用途的通信,并且正在自找麻烦。有没有更好的方法在 Kotlin 中实现这个?另外 - 不允许我在第一个样本中指出的方式背后的原因是什么?接口不只是我需要实现的签名合同,还是我在这里缺少涉及泛型的东西?
是的,您遗漏了 JVM 上泛型实现的一个重要细节:the type erasure。简而言之,classes 的编译字节码实际上不包含任何关于泛型类型的信息(除了一些关于 class 或方法是泛型这一事实的元数据)。所有类型检查都发生在编译时,之后代码中不再保留泛型类型,只有 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()
}
在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和Kotlinhas method overloading based on return value type都不会,而且参数有时也会有歧义,所以多重继承会受到很大限制。
然而,事情会随着 Project Valhalla 而改变(具体化的泛型将在运行时保留实际类型),但我仍然不希望有多个泛型接口继承。
我的代码中有一个场景,我希望 class 为两种不同的类型实现一个接口,例如这个例子:
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
我知道一种可能的解决方案是实现以下代码,其中我使用 <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
}
}
然而,这感觉就像我正在删除类型安全和关于 class 用途的通信,并且正在自找麻烦。有没有更好的方法在 Kotlin 中实现这个?另外 - 不允许我在第一个样本中指出的方式背后的原因是什么?接口不只是我需要实现的签名合同,还是我在这里缺少涉及泛型的东西?
是的,您遗漏了 JVM 上泛型实现的一个重要细节:the type erasure。简而言之,classes 的编译字节码实际上不包含任何关于泛型类型的信息(除了一些关于 class 或方法是泛型这一事实的元数据)。所有类型检查都发生在编译时,之后代码中不再保留泛型类型,只有 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()
}
在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和Kotlinhas method overloading based on return value type都不会,而且参数有时也会有歧义,所以多重继承会受到很大限制。
然而,事情会随着 Project Valhalla 而改变(具体化的泛型将在运行时保留实际类型),但我仍然不希望有多个泛型接口继承。