¿Alguna forma de henetworkingar de la misma interfaz genérica dos veces (con types separados) en Kotlin?

Tengo un escenario en mi código donde me gustaría que una class implemente una interfaz para dos types distintos, como este ejemplo:

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 no está satisfecho con esto, citando:

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

Sé que una posible solución es implementar el siguiente código, donde implemento la interfaz con <Any> y luego verifico los types yo mismo y los delego a sus funciones.

 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 } } 

Sin embargo, parece que elimino la security y la comunicación sobre el uso que se le da a la class, y estoy buscando problemas en el futuro. ¿Hay una mejor manera de implementar esto en Kotlin? Además, ¿cuál es el razonamiento detrás de lo que no está permitido de la manera que indiqué en la primera muestra? ¿Las interfaces no son solo un contrato de firmas que estoy obligado a implementar, o hay algo que me falta con generics aquí?

Sí, te falta un detalle importante de la implementación de generics en JVM: el borrado de types . En pocas palabras, el bytecode comstackdo de classs en realidad no contiene ninguna información sobre los types generics (a exception de algunos metadatos sobre el hecho de que una class o un método es genérico). Toda la verificación de types ocurre en time de compilation, y después de eso no hay types generics retenidos en el código, solo hay Object .

Para descubrir el problema en su caso, simplemente mire el bytecode (en IDEA, Tools -> Kotlin -> Show Kotlin Bytecode , o cualquier otra herramienta). Consideremos este simple ejemplo:

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

En el bytecode de Converter el tipo genérico se borra:

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

Y aquí están los methods que se encuentran en el bytecode de 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; ... 

Para henetworkingar la interfaz del Converter , Reverser debería tener un método con la misma firma , es decir, un tipo borrado. Si el método de implementación real tiene una firma diferente, se agrega un método de puente . Aquí vemos que el segundo método en bytecode es exactamente el método bridge (y llama al primero).

Entonces, múltiples implementaciones de interfaces genéricas chocarían trivialmente entre sí, porque solo puede haber un método de puente para una determinada firma.

Además, si fuera posible, ni Java ni Kotlin tienen una sobrecarga de methods basada en el tipo de valor de retorno , y en ocasiones también habría ambigüedad en los arguments, por lo que la inheritance múltiple sería bastante limitada.

Sin embargo, las cosas cambiarán con Project Valhalla (los generics reificados conservarán el tipo real en time de ejecución), pero aún así no esperaría una inheritance de interfaz genérica múltiple.