Java Abstract Class Implementieren einer Schnittstelle mit Generics

70

Ich versuche eine abstrakte Klasse zu definieren, die Comparable implementiert. Wenn ich die Klasse mit folgender Definition definiere:

public abstract class MyClass implements Comparable <MyClass>

Unterklassen müssen implementiert werden compareTo(MyClass object). Stattdessen möchte ich, dass jede Unterklasse implementiert compareTo(SubClass object)wird und ein Objekt ihres eigenen Typs akzeptiert. Wenn ich versuche, die abstrakte Klasse mit etwas zu definieren wie:

public abstract class MyClass implements Comparable <? extends MyClass>

Es wird beanstandet, dass "ein Supertyp keinen Platzhalter angeben darf".

Gibt es eine Lösung?

Cem
quelle

Antworten:

50

Es ist meiner Meinung nach etwas zu ausführlich, funktioniert aber:

public abstract class MyClass<T extends MyClass<T>> implements Comparable<T> {

}

public class SubClass extends MyClass<SubClass> {

    @Override
    public int compareTo(SubClass o) {
        // TODO Auto-generated method stub
        return 0;
    }

}
Whiskysierra
quelle
3
Betrachten Sie (1) Klasse MyImpl1 erweitert MyClass <MyImpl1> {...}; und (2) Klasse MyImpl2 erweitert MyClass <MyImpl1> {public int compareTo (MyImpl1 o) {...}}. MyImpl2 macht nicht das Richtige.
Emory
2
Wenn wir davon ausgehen, dass jede Unterklasse MyClass um eine eigene Klasse als generischen Parameter erweitert, ist die Lösung korrekt. Es scheint jedoch, dass es keine Möglichkeit gibt, dies sicherzustellen, wie Emory betonte.
Cem
@emory Dies gilt auch für die Comparabledirekte Implementierung . Niemand hält dich davon ab MyImpl2 implements Comparable<MyImpl1>.
whiskeysierra
1
Das Deklarieren der Anforderung zwingt Unterklassen jedoch dazu, vergleichbare zu implementieren, anstatt sie anzunehmen .
whiskeysierra
3
public abstract class MyClass<T> implements Comparable<T>ist genauso gut
Newacct
21

Abgesehen von den mechanischen Schwierigkeiten, auf die Sie beim Deklarieren der Signaturen stoßen, macht das Ziel wenig Sinn. Sie versuchen, eine kovariante Vergleichsfunktion einzurichten, die die gesamte Idee der Einrichtung einer Schnittstelle, die abgeleitete Klassen anpassen können, zunichte macht.

Wenn Sie eine Unterklasse SubClassso definieren, dass ihre Instanzen nur mit anderen SubClassInstanzen verglichen werden können , wie wird dann SubClassder durch definierte Vertrag erfüllt MyClass? Denken Sie daran MyClass, dass es und alle daraus abgeleiteten Typen mit anderen MyClassInstanzen verglichen werden können . Sie versuchen , denn das ist nicht wahr zu machen SubClass, was bedeutet , dass SubClassnicht nicht erfüllt MyClassist Vertrag: Sie können nicht ersetzen , SubClassfür MyClass, weilSubClass 's Anforderungen strenger sind.

Dieses Problem konzentriert sich auf Kovarianz und Kontravarianz und darauf, wie sich Funktionssignaturen durch Typableitung ändern können. Sie können sich entspannen eine Anforderung auf ein Argument der Typ Annahme einer breiteren Typ als die Supertyp Unterschrift Anforderungen-und Sie können stärken eine Anforderung auf einem Rückgabetyp versprechende eine schmalere Typ zurückzukehren als die Supertyp Unterschrift. Jede dieser Freiheiten erlaubt immer noch eine perfekte Substitution des abgeleiteten Typs für den Supertyp; Ein Anrufer kann den Unterschied nicht erkennen, wenn er den abgeleiteten Typ über die Schnittstelle des Supertyps verwendet, aber ein Anrufer, der den abgeleiteten Typ konkret verwendet, kann diese Freiheiten nutzen.

Willis Antwort lehrt etwas über generische Deklarationen, aber ich fordere Sie auf, Ihr Ziel zu überdenken, bevor Sie die Technik auf Kosten der Semantik akzeptieren.

seh
quelle
Ich stimme dieser Antwort zu. Außerdem sollten wir für Schnittstellen codieren, nicht für Klassen. Die eigentliche Implementierungsklasse kann eine anonyme Klasse, eine lokale Klasse (in einer Methode verschachtelt), eine private (in einer Klasse verschachtelt) oder ein privates Paket sein und liegt daher nicht in unserem Geltungsbereich.
Emory
2
Ein weiteres Problem, das ich sehe, ist das Speichern der Unterklassenobjekte in einer Sammlung. Ich sollte in der Lage sein, sie mit nur zu speichern List<MyClass>, anstatt List<MyClass<?>>. Und wie würde es heißen, wenn Sie eines dieser Objekte bekommen und anrufen equals(anObject)?
TheLQ
seh, danke für deine antwort. Eigentlich suche ich nach einem Vertrag, der jede Unterklasse dazu zwingt, eine compareTo () -Methode nur für ihre eigene Klasse zu haben, aber nicht für eine andere Unterklasse. Meine erfundene Generika-Definition könnte in diesem Sinne irreführend sein.
Cem
Ich verstehe, Cem, obwohl es immer noch seltsam ist , die compareTo()Definition von Unterklassen zu erzwingen . Mit Enumwird diese Definition für abgeleitete Typen bereitgestellt - Typen, die der Compiler für Sie generiert - und das ist eine ungewöhnliche Situation. In Ihrem Fall stellen Sie nichts anderes als eine Verpflichtung für abgeleitete Typen bereit. Ist die Absicht zu garantieren, dass Sie generische Funktionen gegen Typ schreiben können MyType<T>und sicher sein, dass es bietet compareTo(MyType<T>)? Wenn ja, könnten Sie das nicht auch tun, indem Sie Ihre Funktionsanforderung haben <T extends MyType && Comparable<T>>?
seh
3

siehe Javas eigenes Beispiel:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>
    public final int compareTo(E o)

zu sehs kommentar: normalerweise ist das argument richtig. Generika machen jedoch Typbeziehungen komplizierter. Eine Unterklasse ist möglicherweise kein Subtyp von MyClass in Willis Lösung.

SubClassAist ein Subtyp von MyClass<SubClassA>, aber kein Subtyp vonMyClass<SubClassB>

Typ MyClass<X>definiert einen Vertrag, für compareTo(X)den alle seine Untertypen eingehalten werden müssen. Da gibt es kein Problem.

unwiderlegbar
quelle
1
Das ist ein gutes Beispiel, obwohl es sich von Cems ursprünglicher Frage unterscheidet. Jetzt habe ich seinen Code vielleicht zu wörtlich gelesen; Vielleicht versuchte er genau das zu schreiben. In diesem Fall geht es in der ComparableFacette der EnumSchnittstelle darum, was eine bestimmte Unterklasse mit sich selbst (oder vielmehr mit Instanzen von sich selbst) tun kann, und nicht Enumdarum , was sie mit abgeleiteten Typen im Allgemeinen tun kann . Wenn es das ist, wonach Cem gesucht hat, dann halte ich Ihre Antwort für angemessener als meine.
seh
Ich denke, es ist in Ordnung, solange alle Unterklassen ein Subtyp von MyClass <?> Sind. Ich bin mir jedoch nicht sicher, ob es in zukünftigen Schritten ein Problem verursacht. Dies ist das erste Mal, dass ich mit Generika entwerfe.
Cem
Dies ist ein schlechtes Beispiel. Aufzählung ist ein Sonderfall - Aufzählungstypen werden von der Sprache bereitgestellt und haben eine bestimmte Form (ein enum ATestament implement Enum<A>), die für benutzerdefinierte Klassen im Allgemeinen nicht gilt.
Newacct
1

Ich bin mir nicht sicher, ob Sie die Erfassung benötigen:

Fügen Sie zunächst compareTo zur abstrakten Klasse hinzu ...

public abstract class MyClass implements Comparable <MyClass> {

@Override
public int compareTo(MyClass c) {
...
}    
}

Fügen Sie dann die Implementierungen hinzu ...

public class MyClass1 extends MyClass {
...
}

public class MyClass2 extends MyClass {
...
}

Wenn Sie compare aufrufen, wird die Super-Type-Methode aufgerufen ...

MyClass1 c1 = new MyClass1();
MyClass2 c2 = new MyClass2();

c1.compareTo(c2);
zevra0
quelle
Ist das nicht genau so, wie Cem sein Problem beschrieben hat? Wie würden Sie compareToin MyClass1oder MyClass2mit einem anderen Parametertyp implementieren ?
whiskeysierra
1
public abstract class MyClass<T> implements Comparable<T> {

}

public class SubClass extends MyClass<SubClass> {

    @Override
    public int compareTo(SubClass o) {
        // TODO Auto-generated method stub
        return 0;
    }

}
newacct
quelle
Dies beschränkt den Typparameter T nicht auf Unterklassen von MyClass.
Stevo Slavić
@ StevoSlavić: Also? Es ist vollkommen typsicher.
Newacct
Wenn ich die ursprüngliche Frage / das ursprüngliche Beispiel richtig verstanden habe, bestand eine der Ideen darin, T auf Unterklassen von MyClass zu beschränken, während der Compiler dies nicht zulässt.
Stevo Slavić
@ StevoSlavić: Die Frage möchte, dass jede Unterklasse nur mit dieser Unterklasse vergleichbar ist
newacct
Nichts hindert Sie daran, eine öffentliche Klasse zu schreiben. SubClass erweitert MyClass <WhateverClass> ....
Stevo Slavić
1

Eine andere Lösung gefunden:

  1. Definieren Sie eine Schnittstelle für die Felder, aus denen sich das Comaprable zusammensetzt (z. B. ComparableFoo).
  2. Implementieren Sie die Schnittstelle in der übergeordneten Klasse
  3. Implementieren Sie Comparable für die übergeordnete Klasse.
  4. Schreiben Sie Ihre Implementierung.

Die Lösung sollte folgendermaßen aussehen:

public abstract class MyClass implements ComparableFoo,Comparable<ComparableFoo> {
    public int compareTo(ComparableFoo o) {
    // your implementation
    }
}

Diese Lösung impliziert, dass möglicherweise mehr Dinge ComparableFoo implementieren - dies ist wahrscheinlich nicht der Fall, aber dann codieren Sie in eine Schnittstelle und der generische Ausdruck ist einfach.

David Levy
quelle
Ich mag diese Lösung wegen ihrer Einfachheit
Pehmolelu
0

Ich weiß, dass Sie gesagt haben, Sie möchten "compareTo (SubClass-Objekt), das ein Objekt seines eigenen Typs akzeptiert", aber ich schlage trotzdem vor, die abstrakte Klasse wie folgt zu deklarieren:

public abstract class MyClass implements Comparable <Object>

Führen Sie eine Überprüfung durch, wenn Sie compareTo in MySubClass überschreiben:

@Override
public int compareTo(Object o) {
    if (o instanceof MySubClass)) {
        ...
    }
    else throw new IllegalArgumentException(...)
}

ähnlich wie "gleich" oder "klonen"

Caroline Even
quelle