2016-07-24 13 views
7

Angenommen, wir haben eine übergeordnete Schnittstelle mit der Funktion compare(). AngenommenJava-Polymorphie: Wie kann ich Eingabeparameter für Typumwandlung vermeiden?

public interface Parent { 
    public int compare(Parent otherParent); 
} 

, dass Kinder Child1, Child2, Child3 implementieren diese Schnittstelle Eltern

public class Child1 implements Parent { 
    @Override 
    public int compare(Parent other) { 
     Child1 otherChild = (Child1)other; 
    } 
} 

Auch ich bin mit Generika <T extends Parent> überall sonst im Code. Also muss ich zwei Objekte vom Typ T aus anderen Teilen des Codes vergleichen.

Ich verstehe, dass dies ein schlechter Entwurf ist, da ich das Elternobjekt in compare() -Funktion typecasting bin und ich nicht einmal weiß, ob die Eingabe vom Typ Child1 ist.

Wie kann ich diesen Typ Casting vermeiden, während Generika verwenden?

Antwort

2

Sie können nicht. Java Interfaces sind genau das, was der Name impliziert - eine Schnittstelle diktiert die akzeptierten Typen; also muss genau die gleiche Methodensignatur zur Kompilierzeit wie in der Schnittstelle definiert sein.

Ja, dies auf einen Child-Typ zurückzuführen, fühlt sich an wie ein schlechtes Design - aber es ist wirklich das schlechte Design, das Java vorschreibt, also ist das nicht deine Schuld.

+0

ich glaube, ich dann Typ-Druckguss verwendet werden müssen. Vielen Dank! –

3

Die Antwort hängt davon ab, wie Sie Ihre Vererbungshierarchie behandeln:

  • Wenn abgeleitete Klassen nur auf Instanzen ihrer eigenen Klasse vergleichen müssen, und dürfen einen Fehler werfen, wenn mit dem Objekt einer anderen Klasse präsentiert, Verwenden Sie Cast, aber schützen Sie es mit der Überprüfung instanceof
  • Wenn abgeleitete Klassen über Typgrenzen hinweg verglichen werden müssen, ist eine komplexere Strategie erforderlich, um Casting zu vermeiden: Sie könnten ein Besucher-ähnliches Muster verwenden, um doppelte Disposition für den Vergleich zu implementieren implementieren.

Hier ist eine Beispielimplementierung einer abstrakte Klasse mit:

// Use this class as the base class of your Child classes 
class AbstractChild implements Parent { 
    @Override 
    public int compare(Parent otherObj) { 
     if (!()) { 
      throw new IllegalStateException("Unexpected implementation of Child"); 
     } 
     AbstractChild other = (AbstractChild)otherObj; 
     return doCompare(other); 
    } 
    protected abstract int doCompare(AbstractChild other); 
    protected abstract int accept(Child1 c1); 
    protected abstract int accept(Child2 c2); 
    protected abstract int accept(Child3 c3); 
} 
class Child1 extends AbstractChild { 
    @Override 
    protected int doCompare(AbstractChild other) { 
     return other.accept(this); 
    } 
    @Override 
    protected int accept(Child1 c1) { 
     // Compare c1 instance to this object 
    } 
    @Override 
    protected int accept(Child2 c2) { 
     // Compare c2 instance to this object 
    } 
    @Override 
    protected int accept(Child3 c3) { 
     // Compare c3 instance to this object 
    } 
} 
class Child2 extends AbstractChild { 
    @Override 
    protected int doCompare(AbstractChild other) { 
     return other.accept(this); 
    } 
    @Override 
    protected int accept(Child1 c1) { 
     // Compare c1 instance to this object 
    } 
    @Override 
    protected int accept(Child2 c2) { 
     // Compare c2 instance to this object 
    } 
    @Override 
    protected int accept(Child3 c3) { 
     // Compare c3 instance to this object 
    } 
} 
class Child3 extends AbstractChild { 
    @Override 
    protected int doCompare(AbstractChild other) { 
     return other.accept(this); 
    } 
    @Override 
    protected int accept(Child1 c1) { 
     // Compare c1 instance to this object 
    } 
    @Override 
    protected int accept(Child2 c2) { 
     // Compare c2 instance to this object 
    } 
    @Override 
    protected int accept(Child3 c3) { 
     // Compare c3 instance to this object 
    } 
} 

Der einzige Guss in diesem Ansatz auf der abstrakten Klasse Ebene ist. Die Implementierungen der tatsächlichen Vergleichslogik sind in accept Methoden enthalten, wo der Vergleich zwischen zwei Objekten bekannten Typs durchgeführt wird.

+0

Ich muss zugeben, dass ich etwas im Nachteil bin, was den Vorteil hat, diesen Code im Vergleich zu In-Methode-Casting/Checking zu haben. Ich war in den letzten zehn Jahren nicht sehr viel von einem Java-Experten (wann kam Java 1.4 heraus?), Also: Lässt die JVM die Typvergleiche machen, die notwendig sind, um die richtige Methode wirklich von irgendwelchen Leistungsmerkmalen zu "delegieren" oder hat andere Vorteile (abgesehen davon, dass es wahrscheinlich "das Richtige zu tun" ist, dass eine stark typisierte Sprache mit der Begriffsklärung umgehen kann)? –

+0

@ MarcusMüller Dies ist eine Antwort auf die Frage "Wie vermeiden Sie Cast", die davon ausgeht, dass Sie es aus irgendeinem Grund vermeiden möchten. Ich habe einen ähnlichen Ansatz in der Vergangenheit in sehr spezifischen Situationen verwendet, in denen ich den Besucher für etwas mehr als Vergleich nutzen konnte. Ich bin zu der Zeit, als Java-5 herauskam, von Java zu C# übergegangen, und ich verwendete einen anderen Sprachmechanismus, um meine Mehrfach-Versendung dort zu implementieren (nämlich "dynamisch", das seit C# 4 verfügbar ist). – dasblinkenlight

+0

Zustimmung mit meinem Kommentar ein wenig off-topic :) eindeutig, das Besucher-Muster ist * the * klassischen Ansatz zur Wiederherstellung von Typ Informationen verloren durch Weitergabe von Dingen in einer polymorphen Sprache, die in sauber Umgang mit verschiedenen Typ Informationsobjekte begrenzt ist (Vergleiche Java mit C#, oder sogar dynamisch typisierte Skriptsprachen wie Python, wo ich tatsächlich Typen als Schlüssel für ein callable-Diktat verwenden könnte, wenn ich das wollte. Meine Hoffnung war einfach, dass du ein bisschen Erfahrung dazu haben könntest, was du getan hast :) Danke! –

2

Sie können es im allgemeinen Fall nicht vermeiden. wenn die Anzahl der untergeordneten Klassen festgelegt ist jedoch, können Sie so etwas wie die Besuchermuster gelten auf Kosten von mehr Code zu vermeiden Gießen schreiben:

interface Parent { 
    int compare(Parent p); 
    protected int compareWith(Child1 c); 
    protected int compareWith(Child2 c); 
    protected int compareWith(Child3 c); 
} 

class Child1 implements Parent { 
    @Override int compare(Parent p) { 
     return p.compareWith(this); 
    } 
    @Override int compareWith(Child1 c) { 
     //specific code to child1 
    } 
    @Override int compareWith(Child2 c) { 
     //specific code to child2 
    } 
    @Override int compareWith(Child3 c) { 
     //specific code to child3 
    } 
} 
// and so on for the other children 

Dieses Casting ist zu vermeiden, aber ich bin nicht sicher, dass die Mehr Aufwand lohnt sich in dem Fall, den Sie hier vorgestellt haben.

5

Warum nicht das?

interface Parent<T extends Parent<?>> { 
    int compare(T type); 
} 

class Child1 implements Parent<Child1>{ 

    @Override 
    public int compare(Child1 type) { 
     return 0; 
    } 
} 

Edit: korrekte Verwendung Um sicherzustellen, Sie

interface Parent<T extends Parent<T>>{ /* ... */ } //instead of wildcard 

Aber um ehrlich zu sein, dass „Schleife“ sieht nicht hübsch verwenden können, und da Generics in Java funktioniert nicht bei RunTime(), sie sind im Wesentlichen syntaktisch - Zucker für die gleiche Besetzung, die du "schlechtes Design" nennst, also denke ich nicht, dass deine derzeitige Herangehensweise schlecht ist.

+1

Der 'extend Parent ' Teil sieht tatsächlich nicht hübsch aus. Aber tatsächlich kann es weggelassen werden - unter der Annahme, dass der Implementierer für die Verwendung eines richtigen "T" verantwortlich ist. Dasselbe geschieht in "Vergleichbar". – Marco13

2

Das Muster, das Sie dort beschrieben haben, ist genau das Muster von Comparable. In der Tat sollten Sie in Betracht ziehen, Ihre Parent Schnittstelle wegzulassen und sie stattdessen durch Comparable zu ersetzen.

Die Comparable Schnittstelle und ihre Anwendungen zeigen auch, wie diese gelöst werden können: Der Typ kann als Parameter angegeben werden, um sicherzustellen, dass nur der passende Typ kann auf die compare Methode übergeben werden:

interface Parent<T> { 
    int compare(T that); 
} 

class Child1 implements Parent<Child1> { 
    @Override 
    public int compare(Child1 that) { 
     ... 
    } 
} 

bei allen anderen Fällen würden Sie zumindest darüber nachzudenken, was passieren soll, wenn verschiedene Child -Klassen miteinander verglichen werden:

Child1 c1 = new Child1(); 
Child2 c2 = new Child2(); 

// Should this be possible? What should happen here? 
// How should different child classes be compared? 
c1.compare(c2);