2016-02-20 8 views
11

Ich habe ein Szenario, in meinem Code, wo ich eine Klasse möchte eine Schnittstelle für zwei getrennte Arten, wie in diesem Beispiel implementieren:Jede Möglichkeit, in Kotlin von derselben generischen Schnittstelle zweimal (mit separaten Typen) zu erben?

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 ist nicht zufrieden mit diesem, unter Berufung auf:

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

Ich weiß, dass eine mögliche Lösung ist, den folgenden Code zu implementieren, wo ich die Schnittstelle mit <Any> implementieren und dann die Typen selbst überprüfen und sie an ihre Funktionen delegieren.

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

jedoch, die wie ich fühlt mich Typ Sicherheit und Kommunikation über das Entfernen, was die Klasse für verwendet wird, und für die Bredouille bitte. Gibt es einen besseren Weg, dies in Kotlin umzusetzen? Zusätzlich - was ist der Grund dafür, dass es nicht so ist, wie ich es in der ersten Probe angegeben habe? Sind Interfaces nicht nur ein Vertrag von Signaturen, den ich implementieren muss, oder fehlt mir hier etwas, was Generika betrifft?

Antwort

14

Ja, Sie vermissen ein wichtiges Detail der Generika-Implementierung auf JVM: the type erasure. Kurz gesagt, der kompilierte Bytecode von Klassen enthält eigentlich keine Informationen über generische Typen (außer einigen Metadaten über die Tatsache, dass eine Klasse oder eine Methode generisch ist). Die gesamte Typüberprüfung findet zur Kompilierungszeit statt, und danach bleiben keine generischen Typen im Code erhalten, es gibt nur Object.

Um das Problem in Ihrem Fall zu entdecken, schauen Sie sich einfach den Bytecode an (in IDEA, Tools -> Kotlin -> Show Kotlin Bytecode oder einem anderen Tool). Lassen Sie sich dieses einfache Beispiel betrachten:

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

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

In dem Bytecode von Converter der generischen Typ gelöscht wird:

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

Und hier sind die Methoden in dem Bytecode von Reverser gefunden:

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

Um die Converter Schnittstelle zu erben, sollte Reverser eine Methode mit dem haben gleiche Signatur, das heißt, ein Typ gelöscht. Wenn die tatsächliche Implementierungsmethode eine andere Signatur aufweist, wird bridge method hinzugefügt. Hier sehen wir, dass die zweite Methode im Bytecode genau die Bridge-Methode ist (und die erste Methode aufruft).

So würden mehrere generische Interface-Implementierungen trivial miteinander kollidieren, weil es nur eine Bridge-Methode für eine bestimmte Signatur geben kann.

Darüber hinaus, wenn es möglich wäre, weder Java noch Kotlin has method overloading based on return value type, und es wäre auch manchmal Ambiguität in Argumenten, so dass die Mehrfachvererbung ziemlich begrenzt wäre.

Die Dinge werden sich jedoch mit Project Valhalla ändern (reparierte Generika behalten den tatsächlichen Typ zur Laufzeit), aber ich würde immer noch nicht erwarten, dass mehrere generische Schnittstellen vererbt werden.