有什么方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)? [英] Any way to inherit from same generic interface twice (with separate types) in Kotlin?

查看:21
本文介绍了有什么方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a scenario in my code where I would like a class to implement an interface for two separate types, like this example:

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 对此不满意,引用:

Kotlin is not pleased with this, citing:

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

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

I know that one possible solution is to implement the following code, where I implement the interface with <Any> and then check the types myself and delegate them to their functions.

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 中实现这一点?另外 - 不允许我在第一个样本中指出的方式背后的原因是什么?接口不只是我需要实现的签名契约,还是我在这里缺少涉及泛型的东西?

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?

推荐答案

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

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.

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

To discover the problem in your case, just look at the bytecode (in IDEA, Tools -> Kotlin -> Show Kotlin Bytecode, or any other tool). Let's consider this simple example:

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

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

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

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;

这里是在Reverser的字节码中找到的方法:

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;
    ...

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

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.

此外,如果可能的话,Java 和 Kotlin 都没有基于方法重载在返回值类型上,有时也会出现参数的歧义,所以多重继承会很有限.

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.

然而,事情会随着瓦尔哈拉计划而改变(具体化的泛型将保留实际的在运行时输入),但我仍然不希望有多个通用接口继承.

Things, however, will change with Project Valhalla (reified generics will retain actual type at runtime), but still I wouldn't expect multiple generic interface inheritance.

这篇关于有什么方法可以在 Kotlin 中从同一个通用接口继承两次(使用不同的类型)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆