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 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 } }
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?
It is prohibited that a type implements or extends two different instantiations of the same interface. This is because the bridge method generation process cannot handle this situation.
A class can implement multiple interfaces and many classes can implement the same interface. Final method can't be overridden. Thus, an abstract function can't be final.
First, like Java, a Kotlin class can only inherit one superclass, but it can implement multiple interfaces. Kotlin uses the colon character “:” to indicate both inheritance and interfaces' implementation. So, for example, class MyType(…) : SuperType(…) means the MyType class inherits the SuperType class.
But unlike classes, interfaces can actually inherit from multiple interfaces. This is done by listing the names of all interfaces to inherit from, separated by comma. A class implementing an interface which inherits from multiple interfaces must implement all methods from the interface and its parent interfaces.
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
.
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() }
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With