Wie stelle ich mithilfe compareTo() die natürliche Objektreihenfolge her (Java)?
Ich habe heute über die compareTo()-Methode des Comparable-Interfaces gelesen, man würde sie wie folgt verwenden:
"If your class objects have a natural order, implement the
Comparable<T>
interface and define this method."
Aber wie kann ich compareTo() implementieren, um die natürliche Reihenfolge der Objekte herzustellen?
Beispiel: Ich sollte Studenten nach ihrer Punktzahl sortieren, habe dann compareTo() wie folgt überschrieben:
@Override
public int compareTo(Student student) {
if(this.points > student.points)
return 1; //current student is better
return 0;
}
Allerdings returnt das ja jetzt nur einen bestimmten Wert, mit dem man dann arbeiten könnte, wenn man anschließend die Studenten z.B. in eine Liste packen will, aber es stellt ja nicht bereits in der Methode selbst die Objektreihenfolge her. Ich verstehe auch gar nicht, wie das gehen soll, wenn ich z.B. will, dass die Klasse Student das Interface Comparable implementiert. Ich meine, wenn ich dann ein Objekt erzeuge, dann kann ich auf dieses und ein anderes diese Methode anwenden, aber ich bräuchte ein weiteres Hilfsmittel, um die Studenten zu ordnen, wie eben z.B. besagte Liste.
Ist es also irgendwie möglich, compareTo() so zu überschreiben, dass es automatisch Studenten oder allgemein Objekte in ihre natürliche Reihenfolge bringt oder brauche ich dafür immer noch weitere Hilfsmittel?
Oder habe ich compareTo() auf richtige Weise überschrieben?
LG Kath
PS:
In der Doku steht ebenfalls Folgendes über Comparable():
This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method.
3 Antworten
Noch ein paar zusätzliche Gedanken:
Studenten haben keine natürliche Ordnung. Wenn man dir z.B. 10 Studenten hinstellt und sagt "sortier die mal", dann ist nicht automatisch klar, nach welchem Kriterium sie sortiert werden (es könnte z.B. nach Name (erst Nachname, dann Vorname oder vielleicht auch andersrum), nach Alter, nach Punktzahl, nach Größe, nach Farbe des Pullovers etc. sein). Von daher ist es untypisch, eine Klasse "Student" das Interface Comparable<Student> implementieren zu lassen.
In der Java-Dokumentation steht auch, dass dringend empfohlen wird, dass die compareTo-Funktion konsistent zu der equals-Funktion ist. Also anders gesagt: Wenn die compareTo-Funktion zurückgibt, dass zwei Objekte gleich sind (Rückgabewert 0), dass dann auch die equals-Funktion zurückgeben sollte, dass zwei Objekte gleich sind. Das ist aber nicht der Fall, wenn du zwei Studenten mit der gleichen Punktzahl hast.
Normalerweise würdest du also der Sortierfunktion direkt ein Kriterium mitgeben, nach dem sortiert werden soll. Z.B absteigend nach Punktzahl:
studentenListe.sort(Comparator.comparing(Student::getPunktzahl).reversed());
Nur falls es jetzt für besondere Anlässe eine bestimmte Sortierung gibt (z.B. bei der Zeugnisaugabe am Ende des Studiums sollen alle zuerst absteigend nach Punktzahl, dann nach Nachname und dann nach Vorname sortiert sein), könntest du einen Comparator als öffentliche Konstante in deine Klasse aufnehmen:
public static final Comparator<Student> COMP_ZEUGNISAUSGABE = Comparator.comparing(Student::getPunktzahl).reversed().thenComparing(Student::getNachname).thenComparing(Student::getVorname);
Und noch ein Tipp:
Mal angenommen, Studenten hätten eine natürliche Sortierung nach Punktzahl, würde ich dir empfehlen, die vorhandene compare-Funktion für den jeweiligen Variablentyp zu nehmen. Dadurch vermeidest du Flüchtigkeitsfehler. Z.B. bei int-Werten:
@Override
public int compareTo(Student other) {
return Integer.compare(this.punktzahl, other.punktzahl);
}
Die Aufgabenstellung war halt entsprechend.
Ja, mir ist leider schon öfter aufgefallen, dass Lehrer versuchen, sich irgendwelche Aufgaben auszudenken, die dann völlig absurd sind und die man als Programmierer niemals so bauen würde. In dem Fall kannst du natürlich einfach die Variante nehmen, die ich als letztes in meiner Antwort geschrieben habe.
Wie ändere ich das dann, sodass compareTo() konsistent zu equals() ist?
Im Falle der Studenten geht es gar nicht (bzw. nur mit der Einschränkung, dass zwei Studenten als Gleich angesehen werden, wenn sie die gleiche Punktzahl haben. Wenn du jetzt versuchen würdest, ein HashSet mit Studenten zu füllen, könntest du aber keine zwei Studenten mit gleicher Punktzahl einfügen.)
Bei Klassen, die tatsächlich eine natürliche Ordnung haben (z.B. Zahlen) kannst du einfach die Methode "equals(Object other)" überschreiben, damit sie True zurückgibt, wenn beide Objekte den gleichen Wert haben. Wenn du es einfach haben willst, kannst du da auch einfach prüfen, ob compareTo() eine 0 zurückgibt.
Was für Flüchtigkeitsfehler vermeide ich damit?
In erster Linie, dass du die Objekte oder die Operatoren nicht vertauschst, also versehentlich die Sortierung umkehrst, oder wie in deinem Fall dass du nur zwei von drei Möglichkeiten abdeckst. Davon abgesehen braucht es auch weniger Hirnschmalz, wenn du einfach die Funktionen benutzt, bei denen sich schon jemand anderes Gedanken drum gemacht hat.
Ich finde z.B. das hier:
return Integer.compare(this.punktzahl, other.punktzahl);
einfacher und übersichtlicher, als das hier:
if (this.punktzahl < other.punktzahl) {
return -1;
} else if (this.punktzahl > other.punktzahl) {
return 1;
} else {
return 0;
}
Eine Alternative wäre auch noch:
return (int) Math.signum(this.punktzahl - other.punktzahl);
oder
return Integer.valueOf(this.punktzahl).compareTo(other.punktzahl);
Nein die compareto soll das gar nicht mache. Und niemand der sie benutzt würde erwarten daß sie irgendwas ordnet. Das geht auch gar nicht.
Du hast die Dokumentation falsch gelesen. Da steht wenn die Objekte eine Reihenfolge haben. Dann kann. Man diese Methode überschreiben.
Orden muss man die Elemente in einer Liste etc. Schon selbst. Oder man verwendet listen etc. Die sich automatisch ordnen die rufen dann ggf implizit die compareto auf.
Bei Studenten könnte man sich eine natürliche Ordnung z.b. nach Namen vorstellen etc.
Das Icomoarwable Interface sagt dem Benutzer im Grunde nur das man diese Objekte einfach ordnen kann. Ohne sich jetzt selbst Gedanken zu machen nach was man diese ordnen möchte.
Doffe Schreibfehler.
Nur das: Comparable<T>
Interfaces werden üblicherweise mit einem I am Anfang angegeben. Ist aber nicht in jeder Sprache so.
Oh. Und deine Überschreibung ist glaube ich nicht ganz korrekt. Compareto gibt 3 mögliche Werte zurück. Bzw würde man das erwarten.
-1 das Element was übergeben würde ist kleiner
0 die Elemente sind gleich.
1 das Element was übergeben wurde ist größer.
Du gibst glaube ich nur 1 und 0 zurück wie ich sehe.
Ja, weil ich das so wollte, dass wenn es kleiner oder gleich groß ist, das gleiche passiert (also wenn es größer ist vorne anfügen, wenn kleiner oder gleich groß hinter dem, womit es verglichen wird. Dadurch ist quasi bei gleichen Werten das vorher Eingefügte vor dem, was danach eingefügt wurde in der Reihenfolge).
Keine gute Idee nach meiner Ansicht. 0 bedeutet gleich. Wenn ich Student A mit 12 Punkte habe. Und Student B mit 9 und dann B mit A vergleiche. Bekomme ich das Ergebniss gleich. Wenn ich aber A mit B vergleiche dann das Ergebnis A ist grosser als A.
Wenn du nur wissen willst ob this größer als der Parameter ist dann ist es sinnvoller ne greaterthan Methode zu implementieren.
Wenn du aber Methoden so implementierst das sie Inkonsistente Werte zurück gibt (wie im Beispiel einen unerwarteten Unterschied ob man nun a und B Vergleich und B und A.
Dann wird dich das früher oder später in den Hintern beißen. Weil dann eventuell ganz unerwartet ein program komisches verhalten zeigt und der Fehler Sau schwer zu finden sein wird. Z.b. wenn man B und A als gleich behandelt obwohl B kleiner ist als A. (Compare muss -1 zurückgeben)
Aaaah okay dankeschön! Also man nutzt das dann quasi wenn man weiß, wie die Objekte sortiert sein sollen, um die Sortierung zu prüfen und wenn man weiß wie sie sortiert sein sollten, aber auch weiß, dass sie noch nicht so sortiert sind, um sie entsprechend mithilfe der Methode dann sortiert einzuordnen?
Sie macht Objekte schlichtweg vergleichbar. Ohne das man sich als derjenige der die Objekte benutzt darum kümmern muss.
Als Beispiel sind zahlen ja per se vergleichbar.
Studenten. Oder Autos oder andere Objekte sind es aber nicht von Haus aus. Und genau dafür ist diese Methode gedacht.
Normalerweise gibts noch die methode sort und der übergibt man die methode compareto damit das richtige this.points verglichen werden können bei jedem objekt was übergeben wurde . sprich das ist die sortierfunktion für sort() ;
und woher ist die Methode sort() dann? also woher kommt die?
lesen verstehen
Die compareTo()-Methode
Nun gilt es, das Sortierkriterium der compareTo()-Methode zu implementieren. Beim Aufruf von Arrays.sort() wird diese vom Programm verwendet, um das Array zu sortieren
https://falconbyte.net/blog-java-comparable.php
dankeschön, ich habe es nun verstanden!
Die Aufgabenstellung war halt entsprechend.
Wie ändere ich das dann, sodass compareTo() konsistent zu equals() ist? ist das überhaupt möglich? bedeutet das nicht, dass zwei Studenten nicht die gleiche Punktzahl haben dürfen, denn wenn, dann müsste es der gleiche Student sein?
Was für Flüchtigkeitsfehler vermeide ich damit?
lg Kath