generics - ¿Alguna forma de heredar dos veces la misma interfaz genérica(con tipos separados) en Kotlin?
interface multiple-inheritance (1)
Tengo un escenario en mi código donde me gustaría que una clase implementara una interfaz para dos tipos separados, 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á contento 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 tipos 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 estoy eliminando el tipo de seguridad y comunicación sobre para qué se usa la clase, y estoy pidiendo problemas en el futuro. ¿Hay una mejor manera de implementar esto en Kotlin? Además, ¿cuál es el razonamiento detrás de que no se permita la forma que indiqué en la primera muestra? ¿No son interfaces solo un contrato de firmas que debo implementar, o hay algo que me falta relacionado con los genéricos aquí?
Sí, te estás perdiendo un detalle importante de la implementación de genéricos en JVM: el borrado de tipo . En pocas palabras, el bytecode compilado de las clases no contiene ninguna información sobre los tipos genéricos (excepto por algunos metadatos sobre el hecho de que una clase o un método es genérico). Toda la verificación de tipo ocurre en tiempo de compilación, y después de que ningún tipo genérico retiene en el código, solo hay Object
.
Para descubrir el problema en su caso, solo mire el bytecode (en IDEA, Tools -> Kotlin -> Show Kotlin Bytecode
, o cualquier otra herramienta). Consideremos este ejemplo simple:
interface Converter<T> {
fun convert(t: T): T
}
class Reverser(): Converter<String> {
override fun convert(t: String) = t.reversed()
}
En el bytecode de Converter
se borra el tipo genérico:
// access flags 0x401
// signature (TT;)TT;
// declaration: T convert(T)
public abstract convert(Ljava/lang/Object;)Ljava/lang/Object;
Y aquí están los métodos que se encuentran en el Reverser
de Reverser
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 heredar la interfaz del Converter
, Reverser
debe tener en orden 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 el código de bytes es exactamente el método de puente (y llama al primero).
Por lo tanto, las implementaciones de múltiples interfaces genéricas se enfrentarán trivialmente entre sí, porque solo puede haber un método de puente para una firma determinada.
Además, si fuera posible, ni Java ni Kotlin tienen una sobrecarga de métodos basada en el tipo de valor de retorno , y en ocasiones también habría ambigüedad en los argumentos, por lo que la herencia múltiple sería bastante limitada.
Sin embargo, las cosas cambiarán con el Proyecto Valhalla (los genéricos reificados conservarán el tipo real en el tiempo de ejecución), pero aún así no esperaría la herencia de múltiples interfaces genéricas.