Angenommen, Sie haben einige Objekte mit mehreren Feldern, mit denen sie verglichen werden können:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
Wenn Sie in diesem Beispiel fragen, ob:
a.compareTo(b) > 0
Sie fragen sich vielleicht, ob der Nachname von a vor dem von b steht oder ob a älter als b ist usw.
Was ist der sauberste Weg, um mehrere Vergleiche zwischen diesen Arten von Objekten zu ermöglichen, ohne unnötige Unordnung oder Overhead hinzuzufügen?
java.lang.Comparable
Schnittstelle ermöglicht den Vergleich nur durch ein Feld- Hinzufügen von zahlreichen Methoden zu vergleichen (dh
compareByFirstName()
,compareByAge()
etc ...) ist meiner Meinung nach überladen.
Was ist der beste Weg, um dies zu erreichen?
Antworten:
Sie können ein implementieren,
Comparator
das zweiPerson
Objekte vergleicht , und Sie können so viele Felder untersuchen, wie Sie möchten. Sie können eine Variable in Ihren Komparator einfügen, die angibt, mit welchem Feld verglichen werden soll, obwohl es wahrscheinlich einfacher wäre, nur mehrere Komparatoren zu schreiben.quelle
Mit Java 8:
Wenn Sie Zugriffsmethoden haben:
Wenn eine Klasse Comparable implementiert, kann ein solcher Komparator in der compareTo-Methode verwendet werden:
quelle
(Person p)
ist wichtig für verkettete Komparatoren.Comparator
bei jedem Aufruf neue Instanzen erstellen ?.thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))
- zuerst Feldauswahl , dann KomparatorcompareTo
wie oben gezeigt verwenden,Comparator
wird bei jedem Aufruf der Methode erstellt. Sie können dies verhindern, indem Sie den Komparator in einem privaten statischen Endfeld speichern.Sie sollten implementieren
Comparable <Person>
. Unter der Annahme, dass nicht alle Felder null sind (der Einfachheit halber), dass das Alter ein int ist und das Vergleichsranking das erste, letzte, Alter ist, ist diecompareTo
Methode recht einfach:quelle
(von Möglichkeiten zum Sortieren von Objektlisten in Java anhand mehrerer Felder )
Arbeitscode in diesem Kern
Verwenden von Java 8 Lambda (hinzugefügt am 10. April 2019)
Java 8 löst dies gut durch Lambda (obwohl Guava und Apache Commons möglicherweise noch mehr Flexibilität bieten):
Vielen Dank an @ gaoagongs Antwort unten .
Chaotisch und verschlungen: Sortieren von Hand
Dies erfordert viel Eingabe, Wartung und ist fehleranfällig.
Der reflektierende Weg: Sortieren mit BeanComparator
Dies ist natürlich prägnanter, aber noch fehleranfälliger, da Sie Ihren direkten Verweis auf die Felder verlieren, indem Sie stattdessen Strings verwenden (keine Typensicherheit, automatische Refactorings). Wenn nun ein Feld umbenannt wird, meldet der Compiler nicht einmal ein Problem. Da diese Lösung Reflexion verwendet, ist die Sortierung außerdem viel langsamer.
Anreise: Sortieren mit Google Guavas ComparisonChain
Dies ist viel besser, erfordert jedoch einen Kesselplattencode für den häufigsten Anwendungsfall: Nullwerte sollten standardmäßig weniger bewertet werden. Für Nullfelder müssen Sie Guava eine zusätzliche Anweisung geben, was in diesem Fall zu tun ist. Dies ist ein flexibler Mechanismus, wenn Sie etwas Bestimmtes tun möchten, aber häufig den Standardfall (dh 1, a, b, z, null).
Sortieren mit Apache Commons CompareToBuilder
Wie Guavas ComparisonChain sortiert diese Bibliotheksklasse problemlos nach mehreren Feldern, definiert jedoch auch das Standardverhalten für Nullwerte (z. B. 1, a, b, z, null). Sie können jedoch auch nichts anderes angeben, es sei denn, Sie stellen Ihren eigenen Komparator bereit.
So
Letztendlich kommt es auf den Geschmack und das Bedürfnis nach Flexibilität (Guavas ComparisonChain) im Vergleich zu prägnantem Code (Apaches CompareToBuilder) an.
Bonusmethode
Ich fand eine schöne Lösung , dass mehrere Komparatoren in der Reihenfolge ihrer Priorität Mähdrescher auf Codereview in einem
MultiComparator
:Natürlich hat Apache Commons Collections bereits einen Nutzen dafür:
ComparatorUtils.chainedComparator (compareatorCollection)
quelle
@Patrick Um mehr als ein Feld nacheinander zu sortieren, versuchen Sie es mit ComparatorChain
quelle
Eine weitere Option, die Sie immer in Betracht ziehen können, ist Apache Commons. Es bietet viele Optionen.
Ex:
quelle
Sie können sich auch Enum ansehen, das Comparator implementiert.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
z.B
quelle
quelle
Für diejenigen, die die Java 8-Streaming-API verwenden können, gibt es einen übersichtlichen Ansatz, der hier gut dokumentiert ist: Lambdas und Sortierung
Ich habe nach dem Äquivalent zum C # LINQ gesucht:
Ich habe den Mechanismus in Java 8 im Komparator gefunden:
Hier ist also das Snippet, das den Algorithmus demonstriert.
Schauen Sie sich den Link oben an, um eine übersichtlichere Art und Weise zu erfahren und zu erklären, wie die Java-Typinferenz die Definition im Vergleich zu LINQ etwas umständlicher macht.
Hier ist der vollständige Unit-Test als Referenz:
quelle
Das
Comparator
manuelle Schreiben eines solchen Anwendungsfalls ist eine schreckliche Lösung, IMO. Solche Ad-hoc-Ansätze haben viele Nachteile:Was ist die Lösung?
Zuerst eine Theorie.
Bezeichnen wir den Satz "Typ
A
unterstützt Vergleich" mitOrd A
. (Aus Programmsicht können Sie sichOrd A
ein Objekt vorstellen, das Logik zum Vergleichen von zweiA
s enthält. Ja, genau wieComparator
.)Wenn
Ord A
undOrd B
, dann sollte ihr Verbund(A, B)
auch den Vergleich unterstützen. dhOrd (A, B)
. WennOrd A
,Ord B
undOrd C
dannOrd (A, B, C)
.Wir können dieses Argument auf eine willkürliche Arität ausweiten und sagen:
Ord A, Ord B, Ord C, ..., Ord Z
⇒Ord (A, B, C, .., Z)
Nennen wir diese Aussage 1.
Der Vergleich der Verbundwerkstoffe funktioniert genau so, wie Sie es in Ihrer Frage beschrieben haben: Der erste Vergleich wird zuerst versucht, dann der nächste, dann der nächste und so weiter.
Das ist der erste Teil unserer Lösung. Nun zum zweiten Teil.
Wenn Sie das wissen
Ord A
, und wissen , wie man transformierenB
zuA
(Anruf , dass Transformationsfunktionf
), dann können Sie auch habenOrd B
. Wie? Nun, wenn die beidenB
Instanzen verglichen werden sollen, transformieren Sie sie zuerst inA
usingf
und wenden sie dann anOrd A
.Hier ordnen wir die Transformation
B → A
zuOrd A → Ord B
. Dies wird als kontravariante Zuordnung (odercomap
kurz) bezeichnet.Ord A, (B → A)
⇒ comapOrd B
Nennen wir diese Aussage 2.
Wenden wir dies nun auf Ihr Beispiel an.
Sie haben einen Datentyp mit dem Namen
Person
, der drei Typfelder umfasstString
.Das wissen wir
Ord String
. Mit Aussage 1 ,Ord (String, String, String)
.Wir können leicht eine Funktion von
Person
bis schreiben(String, String, String)
. (Geben Sie einfach die drei Felder zurück.) Da wir wissenOrd (String, String, String)
undPerson → (String, String, String)
nach Aussage 2 können wir verwendencomap
, um zu erhaltenOrd Person
.QED.
Wie implementiere ich all diese Konzepte?
Die gute Nachricht ist, dass Sie nicht müssen. Es gibt bereits eine Bibliothek, die alle in diesem Beitrag beschriebenen Ideen umsetzt. (Wenn Sie neugierig sind, wie diese implementiert werden, können Sie unter die Haube schauen .)
So sieht der Code damit aus:
Erläuterung:
stringOrd
ist ein Objekt vom TypOrd<String>
. Dies entspricht unserem ursprünglichen Vorschlag "unterstützt den Vergleich".p3Ord
ist ein Verfahren , das erfordertOrd<A>
,Ord<B>
,Ord<C>
und kehrt zurückOrd<P3<A, B, C>>
. Dies entspricht Aussage 1. (P3
steht für Produkt mit drei Elementen. Produkt ist ein algebraischer Begriff für Verbundwerkstoffe.)comap
entspricht gut ,comap
.F<A, B>
repräsentiert eine TransformationsfunktionA → B
.p
ist eine Factory-Methode zum Erstellen von Produkten.Hoffentlich hilft das.
quelle
Anstelle von Vergleichsmethoden möchten Sie möglicherweise nur mehrere Arten von "Komparator" -Unterklassen innerhalb der Person-Klasse definieren. Auf diese Weise können Sie sie an Standard-Sortiermethoden für Sammlungen übergeben.
quelle
Ich denke, es wäre verwirrender, wenn Ihr Vergleichsalgorithmus "clever" wäre. Ich würde mich für die zahlreichen von Ihnen vorgeschlagenen Vergleichsmethoden entscheiden.
Die einzige Ausnahme für mich wäre Gleichheit. Für Unit-Tests war es für mich hilfreich, die .Equals (in .net) zu überschreiben, um festzustellen, ob mehrere Felder zwischen zwei Objekten gleich sind (und nicht, ob die Referenzen gleich sind).
quelle
Wenn es mehrere Möglichkeiten gibt, wie ein Benutzer eine Person bestellen kann, können Sie auch mehrere Komparatoren als Konstanten irgendwo einrichten . Die meisten Sortiervorgänge und sortierten Sammlungen verwenden einen Komparator als Parameter.
quelle
quelle
Die Code-Implementierung desselben ist hier, wenn wir das Personenobjekt nach mehreren Feldern sortieren müssen.
quelle
quelle
Wenn Sie die Schnittstelle " Vergleichbar" implementieren, möchten Sie eine einfache Eigenschaft auswählen, nach der sortiert werden soll. Dies ist als natürliche Ordnung bekannt. Betrachten Sie es als Standard. Es wird immer verwendet, wenn kein spezifischer Komparator geliefert wird. Normalerweise ist dies ein Name, aber Ihr Anwendungsfall erfordert möglicherweise etwas anderes. Es steht Ihnen frei, eine beliebige Anzahl anderer Komparatoren zu verwenden, die Sie verschiedenen Sammlungs-APIs zur Verfügung stellen können, um die natürliche Reihenfolge zu überschreiben.
Beachten Sie auch, dass normalerweise a.equals (b) == true ist, wenn a.compareTo (b) == 0 ist. Es ist in Ordnung, wenn nicht, aber es gibt Nebenwirkungen, die Sie beachten müssen. Sehen Sie sich die hervorragenden Javadocs auf der vergleichbaren Oberfläche an und Sie werden viele großartige Informationen dazu finden.
quelle
Der folgende Blog gibt ein gut verkettetes Vergleichsbeispiel
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
Komparator anrufen:
quelle
Ausgehend von Steves Antwort kann der ternäre Operator verwendet werden:
quelle
Es ist einfach, zwei Objekte mit der Hashcode-Methode in Java zu vergleichen
quelle
Normalerweise überschreibe ich meine
compareTo()
Methode so, wenn ich mehrstufig sortieren muss.Hier wird zuerst der Name des Films, dann der Künstler und zuletzt songLength bevorzugt. Sie müssen nur sicherstellen, dass diese Multiplikatoren weit genug entfernt sind, um die Grenzen des anderen nicht zu überschreiten.
quelle
Mit der Guava-Bibliothek von Google ist dies ganz einfach .
z.B
Objects.equal(name, name2) && Objects.equal(age, age2) && ...
Mehr Beispiele:
quelle