2016-01-05 3 views
17

Ich frage mich, ob es überhaupt ein gültiger Anwendungsfall ist für den folgenden:Soll Vergleichbar jemals mit einem anderen Typ verglichen werden?

class Base {} 

class A implements Comparable<Base> { 
    //... 
} 

Es scheint ein gemeinsames Muster zu sein (Collections für eine Reihe von Beispielen sehen) eine Sammlung von Typ T, wo T extends Comparable<? super T> zu akzeptieren .

Aber es scheint technisch unmöglich, den Vertrag von compareTo() im Vergleich zu einer Basisklasse zu erfüllen, weil es keine Möglichkeit gibt sicherzustellen, dass eine andere Klasse die Basis nicht mit einem widersprüchlichen Vergleich erweitert. Betrachten Sie das folgende Beispiel:

class Base { 
    final int foo; 
    Base(int foo) { 
     this.foo = foo; 
    } 
} 

class A extends Base implements Comparable<Base> { 
    A(int foo) { 
     super(foo); 
    } 
    public int compareTo(Base that) { 
     return Integer.compare(this.foo, that.foo); // sort by foo ascending 
    } 
} 

class B extends Base implements Comparable<Base> { 
    B(int foo) { 
     super(foo); 
    } 
    public int compareTo(Base that) { 
     return -Integer.compare(this.foo, that.foo); // sort by foo descending 
    } 
} 

Wir haben zwei Klassen erstrecken Base anhand von Vergleichen, die eine gemeinsame Regel nicht folgen (wenn es eine gemeinsame Regel, wäre es mit ziemlicher Sicherheit in Base umgesetzt werden). Doch die folgende gebrochene Art kompiliert:

Collections.sort(Arrays.asList(new A(0), new B(1))); 

Wäre es nicht sicherer sein, nur T extends Comparable<T> zu akzeptieren? Oder gibt es einen Anwendungsfall, der den Platzhalter validieren würde?

+0

Mein Bauch sagt Nein (Äpfel und Orangen und all das), aber der einzige Grund, warum Sie dies als "möglich" betrachten, ist, wenn Sie die Kindklassen NIEMALS auf eine andere Weise vergleichen wollten als die Elternklasse. Natürlich könntest du immer nur 'super.compareTo' nennen, statt – MadProgrammer

+0

. Ich denke, du möchtest deinen Titel vielleicht etwas klarstellen. Ich denke, die interessantere Frage, die in Ihrem Beitrag aufgeworfen wird, lautet: "Warum sind Methoden in Sammlungen, die deklariert werden, um zu arbeiten?" T extends Comparable "anstelle von" T extends Comparable "?" Natürlich ist das kein großartiger Titel, aber ich denke, ein Titel, der so orientiert ist, könnte besser sein. – Others

+0

Ich kenne Java 'enum's benutzen ihre Basisklasse für ein' Comparable'. Alle Java 'Enum's sind von der Basisklasse' java.lang.Enum' abgeleitet, die 'Comparable 'implementiert, wobei' E' die Kindklasse ist (d. H. Die von Ihnen verwendete Enumeration). – markspace

Antwort

10

Das ist eine sehr gute Frage. Lassen Sie uns zunächst mit der Grund, warum Collections Verwendung Methoden wie

binarySearch(List<? extends Comparable<? super T>> list, T key) 

der Tat beginnen, warum nicht nur

binarySearch(List<? extends Comparable<T>> list, T key) 

Der Grund hierfür ist die PECS Prinzip: Hersteller erweitert, Consumer ist super. Was macht binarySearch? Es liest Elemente aus der Liste und vergleicht sie dann durch übergibt ihre Werte an die compareTo Funktion. Da es Elemente liest, fungiert die Liste als Produzent, und daher der erste Teil-Producer Extends. Dieser ist offensichtlich, also was ist mit dem Consumer-Super-Teil?

Consumer Super bedeutet im Grunde, dass wenn Sie nur Werte an eine Funktion übergeben wollen, es Ihnen nicht wirklich wichtig ist, ob sie den genauen Typ Ihres Objekts oder eine Superklasse davon akzeptiert. Was die binarySearch Deklaration sagt ist: Ich kann nach etwas suchen, solange irgendetwas an die compareTo Methode der Listenelemente übergeben werden kann.

Bei der Sortierung ist das nicht so offensichtlich, weil die Elemente nur miteinander verglichen werden. Aber selbst dann, was, wenn Base tatsächlich implementiert Comparable<Base> (und macht den gesamten Vergleich) und A und B nur Base ohne Vergleich in keiner Weise zu erweitern? Dann könnten Sie die Listen A und B nicht sortieren, da sie Comparable<A> bzw. Comparable<B> nicht implementieren. Sie müssten die gesamte Schnittstelle jedes Mal neu implementieren, wenn Sie Unterklassen bilden!

Ein weiteres Beispiel: Was ist, wenn jemand eine binäre Suche auf einer Liste mit Instanzen einer Klasse, die nicht einmal erweitern Base tun wollte?

class BaseComparable implements Comparable<Base> { 
    private final Base key; 
    // other fields 

    BaseComparable(Base key, ...) { 
     this.key = key; 
     // other fields initialization 
    } 

    @Override 
    public int compareTo(Base otherKey) { 
     return this.key.compareTo(otherKey); 
    } 
}; 

Und jetzt wollen sie eine Instanz von A als Schlüssel für diese binäre Suche verwenden.Sie können es nur wegen des ? super T Teils tun. Beachten Sie, dass diese Klasse keine Ahnung hat, ob der Schlüssel ein A oder ein B ist, so dass es unmöglich Comparable<A/B> implementieren kann.

Wie für Ihr Beispiel, ich denke, es ist nur ein Beispiel für schlechtes Design. Leider sehe ich keine Möglichkeit, solche Dinge zu verhindern, ohne das PECS-Prinzip zu brechen und/oder die bereits begrenzte Funktionalität von Java-Generika einzuschränken.

0

Der Constraint

T extends Comparable<? super T> 

sollten als

T extends S, S extends Comparable<S> 

A Comparable Typ ist immer selbst Vergleich zu verstehen sein.

Wenn X <: Comparable<Y>, muss X <: Y <: Comparable<Y> der Fall sein.

Wir könnten sogar das aus dem Vertrag von compareTo() "beweisen". Da der umgekehrte y.compareTo(x) definiert werden muss, muss es stimmen, dass Y <: Comparable<A>, X<:A. Nach dem gleichen Argument haben wir A <: Comparable<Y>. Dann führt die Transitivität zu A=Y.

+0

Der Vertrag erfordert technisch nicht, dass 'y.compareTo (x)' definiert wird, obwohl es impliziert ist. – shmosel

+0

kann den Vertrag nicht wirklich erfüllen, wenn 'y.compareTo (x)' nicht definiert ist :) – ZhongYu