Was bedeutet Vergleich, der mit Gleichem übereinstimmt? Was kann passieren, wenn meine Klasse diesem Prinzip nicht folgt?

8

Aus dem JavaDoc von TreeMap:

  

Beachten Sie, dass die Sortierung durch eine sortierte Karte beibehalten wird (ob oder nicht)   expliziter Komparator wird bereitgestellt) muss mit equals übereinstimmen, wenn   Diese sortierte Map soll die Map-Schnittstelle korrekt implementieren. (Sehen   Comparable oder Comparator für eine genaue Definition von konsistent mit   equals.) Dies ist so, weil die Map-Schnittstelle in Bezug auf definiert ist   die Gleich-Operation, aber eine Karte führt alle Schlüsselvergleiche unter Verwendung seiner durch   compareTo (oder compare) -Methode, also zwei Schlüssel, die gleich sind   diese Methode ist vom Standpunkt der sortierten Karte gleich. Das   Das Verhalten einer sortierten Karte ist gut definiert, selbst wenn sie geordnet ist   unvereinbar mit Gleichen; es versäumt es einfach, den allgemeinen Vertrag zu befolgen   der Map-Schnittstelle.

Kann jemand ein konkretes Beispiel geben, um das Problem zu demonstrieren, das auftreten könnte, wenn die Bestellung nicht mit Gleichen übereinstimmt? Nimm zum Beispiel eine benutzerdefinierte Klasse, die eine natürliche Reihenfolge hat, d. H. Sie implementiert Vergleichbar. Behalten auch alle internen Klassen in JDK diese Invariante?

    
Geek 25.08.2012, 17:02
quelle

3 Antworten

10

Der Vertrag der Vergleichsschnittstelle ermöglicht nicht konsistente Ergebnisse Verhalten:

  

Es wird dringend empfohlen (obwohl nicht erforderlich), dass natürliche Ordnungen konsistent mit equals sind.

Theoretisch ist es also möglich, dass eine Klasse im JDK eine compareTo nicht konsistent mit equals hat. Ein gutes Beispiel ist BigDecimal .

Unten ist ein künstliches Beispiel eines Komparators, der nicht mit equals konsistent ist (es besagt grundsätzlich, dass alle Strings gleich sind).

Ausgabe:

  

Größe: 1
  Inhalt: {a = b}

%Vor%     
assylias 25.08.2012, 17:09
quelle
16

Angenommen, diese einfache Student Klasse implementiert Comparable<Student> , überschreibt jedoch nicht equals() / hashCode() . Natürlich ist equals() nicht konsistent mit compareTo() - zwei verschiedene Schüler mit demselben age sind nicht gleich:

%Vor%

Wir können es sicher in TreeMap<Student, String> verwenden:

%Vor%

Die Ergebnisse lassen sich leicht vorhersagen: Die Schüler sind nach ihrem Alter sortiert (obwohl sie in unterschiedlicher Reihenfolge eingefügt wurden) und der Schüler, der new Student(22) key verwendet, funktioniert ebenfalls und gibt "twenty two" zurück. Das bedeutet, dass wir Student class in TreeMap sicher verwenden können.

Aber ändern Sie students in HashMap und die Dinge werden schlecht:

%Vor%

Offensichtlich gibt die Aufzählung von Elementen wegen Hashing eine "zufällige" Reihenfolge zurück - das ist in Ordnung, es verletzt keinen Map Vertrag. Aber die letzte Aussage ist komplett gebrochen. Da HashMap für das Vergleichen von Instanzen equals() / hashCode() verwendet, schlägt das Abrufen des Werts von new Student(22) key fehl und gibt null !

zurück

Dies versucht JavaDoc zu erklären: Solche Klassen funktionieren mit TreeMap , können aber möglicherweise nicht mit anderen Map Implementierungen arbeiten. Beachten Sie, dass Map -Operationen dokumentiert und in equals() / hashCode() definiert sind, z. containsKey() :

  

[...] gibt genau dann true zurück, wenn diese Karte eine Zuordnung für einen Schlüssel k enthält, so dass (key==null ? k==null : key.equals(k))

Daher glaube ich nicht, dass es JDK-Standardklassen gibt, die Comparable implementieren, aber equals() / hashCode() pair nicht implementieren.

    
Tomasz Nurkiewicz 25.08.2012 17:16
quelle
5

Hier ist ein weiteres Beispiel dafür, wann die Konsistenz mit der gleichwertigen UND Gesamtordnung wichtig ist.

Angenommen, wir haben ein Objekt MyObject mit zwei Feldern: id und quantity . id , wie der Name sagt, ist der natürliche Schlüssel des Objekts und quantity ist nur ein Attribut.

%Vor%

Stellen wir uns vor, wir möchten eine Sammlung von MyObject nach quantity absteigend sortieren. Der erste Komparator, den wir schreiben können, ist:

%Vor%

Die Verwendung von MyObject Instanzen, die mit diesem Komparator in einem TreeMap / TreeSet ausgestattet sind, schlägt fehl, weil der Komparator nicht konsistent mit equals ist (siehe vollständigen Code unten). Lassen Sie uns es mit Gleichen konsistent machen:

%Vor%

Dies passt jedoch nicht wieder in TreeSet / TreeMap! (siehe vollständigen Code unten) Dies liegt daran, dass die Ordnungsbeziehung nicht total ist, d. H. Keine zwei Objekte können strikt in eine Ordnungsbeziehung gesetzt werden. Wenn quantity Felder in diesem Komparator gleich sind, ist die resultierende Reihenfolge unbestimmt.

Ein besserer Komparator wäre:

%Vor%

Dieser Komparator stellt sicher, dass:

  • Wenn compareTo 0 zurückgibt, bedeutet dies, dass zwei Objekte equal sind (anfängliche Überprüfung auf Gleichheit)
  • Alle Elemente werden vollständig geordnet, indem id als diskriminierendes Sortierfeld verwendet wird, wenn quantity gleich sind

Vollständiger Testcode:

%Vor%

Ausgabe:

%Vor%

Fazit:

Obwohl ich denke, dass es sehr legitim ist, die Identität von der konzeptionellen Ordnung zu trennen. Zum Beispiel in Bezug auf relationale Datenbanken:

%Vor%

funktioniert perfekt. Objektidentität ist uns egal, und wir wollen keine totale Ordnung.

Aufgrund von Einschränkungen bei der Implementierung von baumbasierten Sammlungen muss jedoch sichergestellt werden, dass jeder Komparator, den sie schreiben,

ist
  • ist Konsistenz mit equals
  • bietet eine Gesamtbestellung über alle möglichen Objekte
Ngx472 04.01.2015 09:53
quelle

Tags und Links