Was sind die Alternativen, um die Gleichheit zweier Objekte zu vergleichen?

StackOverflow https://stackoverflow.com/questions/360265

  •  21-08-2019
  •  | 
  •  

Frage

http://leepoint.net/notes-java/data/expressions /22compareobjects.html

  

Es stellt sich heraus, dass die Definition equals ()   ist nicht trivial; in der Tat ist es mäßig   schwer es richtig zu machen, vor allem in   der Fall von Unterklassen. Das beste   Behandlung der Themen ist in   Horstmann Core Java Vol 1.

Wenn equals () muss immer außer Kraft gesetzt werden, was ist dann ein guter Ansatz für nicht in der Enge getrieben wird, den Objektvergleich zu tun? Was sind einige gute "Design" Alternativen?

EDIT:

Ich bin nicht sicher, ob dies über den Weg kommt, dass ich beabsichtigt hatte. Vielleicht sollte die Frage nach dem Vorbild der mehr sein „Warum wollen Sie zwei Objekte vergleichen?“ Basierend auf Ihrer Antwort auf diese Frage gibt es eine alternative Lösung zum Vergleich? Ich meine nicht, eine andere Implementierung von Gleichen. Ich meine, durchaus nicht mit Gleichheit. Ich glaube, der entscheidende Punkt ist, mit dieser Frage zu beginnen, warum sollten Sie zwei Objekte vergleichen möchten.

War es hilfreich?

Lösung

  

Wenn equals () muss immer außer Kraft gesetzt werden,   dann, was ist ein guter Ansatz für nicht   werden in mit Enge getrieben zu tun   Objektvergleich?

Sie irren sich. Sie sollten so selten wie möglich außer Kraft entspricht.


All Info kommt von Effective Java, Second Edition ( Josh Bloch ). Die erste Ausgabe Kapitel über diese noch verfügbar als kostenloses rel="nofollow Download .

Von Effective Java:

  

Der einfachste Weg, um Probleme zu vermeiden ist   nicht die Methode equals außer Kraft zu setzen, in   wobei jede Instanz der Klasse   nur sich selbst gleich ist.

Das Problem mit beliebig überschreiben equals / hashCode ist Vererbung. Einige gleich Implementierungen Anwalt es wie folgt testen:

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

In der Tat, der Eclipse- (3.4) Java-Editor genau dies tut, wenn Sie die Methode der Erzeugung Source-Tools. Nach Bloch, ist dies ein Fehler, da es die Liskov Substitutionsprinzip verletzt.

Von Effective Java:

  

Es gibt keine Möglichkeit, eine verlängern   instantiable Klasse und Wert hinzufügen   Komponente unter Wahrung der Gleichgestellten   Vertrag.

Zwei Wege Gleichheit Probleme zu minimieren, sind in den beschriebenen Klassen und Interfaces Kapitel:

  1. Favor Zusammensetzung über Vererbung
  2. Entwurf und Dokument für die Vererbung oder sonst verbietet es

Soweit ich sehen kann, ist die einzige Alternative Gleichheit in einer Form außerhalb der Klasse zu testen, und wie diese durchgeführt werden würde würde auf der Gestaltung des Typs abhängig und Kontext Sie es in nutzen versucht.

Zum Beispiel könnten Sie eine Schnittstelle definieren, die dokumentieren, wie sie verglichen werden sollten. Im folgenden Code könnten, Service-Instanzen mit einer neueren Version derselben Klasse zur Laufzeit ersetzt werden - in diesem Fall verschiedene Classloader haben, gleich Vergleiche würden immer false zurück, so überwiegende gleich / hashCode überflüssig wäre

.
public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

  

„Warum wollen Sie zwei Objekte vergleichen?“

Das offensichtlichste Beispiel ist zu prüfen, ob zwei Strings gleich sind (oder zwei Dateien oder URIs ). Zum Beispiel, was passiert, wenn Sie eine Reihe von Dateien erstellen bis analysieren wollen. Per Definition enthält das Set nur einzigartige Elemente. Java Set basiert auf dem Gleichheits / hashCode Methoden Einzigartigkeit seiner Elemente zu erzwingen.

Andere Tipps

Ich glaube nicht, es ist wahr, dass equals immer außer Kraft gesetzt werden soll. Die Regel, wie ich es verstehe, ist, dass übergeordnete gleich in Fällen nur dann sinnvoll ist, wo man klar ist, wie semantisch äquivalente Objekte zu definieren. In diesem Fall außer Kraft setzen Sie hashCode () als auch so, dass Sie keine Objekte, die Sie äquivalent als Rückkehr verschiedenen Hashcodes definiert haben.

Wenn Sie nicht sinnvoll Äquivalenz definieren kann, sehe ich nicht den Nutzen.

Wie wäre es nur tun es richtig?

Hier ist meine gleich Template, das Wissen von Effective Java von Josh Bloch angewendet wird. Lesen Sie das Buch für weitere Informationen:

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

Mmhh

In einigen Fällen können Sie das Objekt als nicht änderbar machen (read-only) und hat es von einem einzigen Punkt erstellt (a Factory-Methode)

Wenn zwei Objekte mit den gleichen Eingangsdaten (Erstellungsparameter) werden die Fabrik benötigt die gleiche Instanz ref zurückkehrt und dann mit „==“ wäre genug.

Dieser Ansatz ist sinnvoll, unter bestimmten Umständen nur. Und die meisten der Zeit würde Overkill aussehen.

Hier finden Sie aktuelle diese Antwort zu wissen, wie zu implementieren solch eine Sache.

Warnung es eine Menge Code ist

Für Kurz sehen, wie die Wrapper-Klasse funktioniert seit Java 1.5

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

ist wahr, während

new Integer( 2 ) == new Integer( 2 )  

ist falsch.

Es hält intern die Referenz und schicken Sie es, wenn der Eingangswert gleich ist.

Wie Sie wissen, Integer schreibgeschützt

Etwas Ähnliches geschieht mit der String-Klasse, von der diese Frage ging.

Vielleicht den Punkt fehle ich aber der einzigen Grund, gleich im Gegensatz zu verwenden, um Ihre eigene Methode mit einem anderen Namen zu definieren ist, weil viele der Sammlungen (und wahrscheinlich auch andere Sachen im JDK oder was auch immer es ist in diesen Tagen genannt) erwartet, dass die Methode equals ein kohärentes Ergebnis zu definieren. Aber darüber hinaus kann ich mir vorstellen drei Arten von Vergleichen, die Sie in Gleichen tun:

  1. Die beiden Objekte sind wirklich die gleiche Instanz. Das macht keinen Sinn zu verwenden, ist gleich, weil Sie == verwenden können. Auch und mich korrigieren, wenn ich vergessen habe, wie es funktioniert in Java, die Standard-Methode equals tut dies den automatisch erzeugten Hash-Codes.
  2. Die beiden Objekte haben Verweise auf die gleichen Instanzen, sind aber nicht die gleiche Instanz. Dies ist nützlich, äh, manchmal ... vor allem, wenn sie Objekte werden beibehalten und in der DB auf das gleiche Objekt beziehen. Sie müßten Ihre Methode equals definieren, dies zu tun.
  3. Die beiden Objekte haben Verweise auf Objekte, die im Wert gleich sind, obwohl sie die gleichen Instanzen können oder nicht sein (mit anderen Worten, Sie Wert der ganzen Weg durch die Hierarchie zu vergleichen).

Warum wollen Sie zwei Objekte vergleichen? Nun, wenn sie gleich sind, dann würden Sie wollen, eine Sache zu tun, und wenn sie es nicht sind, würden Sie wollen, etwas anderes zu tun.

Das heißt, es hängt davon ab, den Fall zur Hand.

Der Hauptgrund, außer Kraft zu setzen equals () in den meisten Fällen nach Duplikaten innerhalb bestimmter Sammlungen zu überprüfen ist. Zum Beispiel, wenn Sie ein Set verwenden, um ein Objekt zu enthalten, die Sie erstellt haben Sie equals überschreiben müssen () und hashCode () in Ihrem Objekt. Gleiches gilt, wenn Sie Ihr benutzerdefiniertes Objekt als Schlüssel in einer Karte verwenden möchten.

Dies ist wichtig, da ich viele Leute den Fehler in der Praxis machen gesehen habe ihre eigenen Objekte zu Sets oder Karten hinzuzufügen, ohne zwingenden equals () und hashCode (). Der Grund, warum dies besonders heimtückisch sein kann, ist der Compiler nicht beschweren, und Sie können mit mehreren Objekten am Ende, die die gleichen Daten enthalten, haben aber unterschiedliche Referenzen in einer Sammlung, die nicht Duplikate nicht zulässt.

Wenn Sie zum Beispiel eine einfache Bohne genannt NameBean mit einem einzelnen String Attribute ‚namen‘ haben, Sie zwei Instanzen von NameBean konstruieren könnten (zB name1 und name2), das jeweils mit dem gleichen ‚namen‘ Attributwert (zB „Alice“ ). Sie könnten dann beide name1 und name2 zu einem Set hinzufügen und die Menge wäre Größe 2 anstatt Größe 1, das ist, was beabsichtigt ist. Ebenso, wenn Sie eine Karte wie Karte haben, um den Namen Bohne zu einem anderen Objekt auf der Karte, und Sie zuerst name1 auf die Zeichenfolge „ersten“ abgebildet und später abgebildet name2 auf die Zeichenfolge „zweite“ haben Sie beiden Schlüssel / Wert-Paare in der Karte (zB name1 -> "erster", name2 -> "zweite"). Also, wenn Sie eine Karte tun Nachschlag wird es den Wert auf die genaue Referenz abgebildet kehren Sie in übergeben, die entweder name1, name2 oder eine andere Referenz mit dem Namen „Alice“, die null zurück.

Hier ist ein konkretes Beispiel durch die Ausgabe voran es ausgeführt wird:

Ausgabe:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

Code:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

Gleichheit ist von grundlegender Bedeutung für Logik (Gesetz der Identität sehen), und es gibt nicht viel Programmierung, ohne sie tun können. Wie für Instanzen von Klassen zu vergleichen, die Sie schreiben, ist gut, dass bei Ihnen. Wenn Sie sie in einer Sammlung in der Lage sein müssen, finden oder sie als Schlüssel in Karten verwenden, werden Sie Gleichheit Kontrollen müssen.

Wenn Sie mehr als ein paar nicht-trivialen Bibliotheken in Java geschrieben haben, werden Sie wissen, dass die Gleichstellung hart ist richtig zu machen, vor allem, wenn die einzigen Werkzeuge in der Brust sind equals und hashCode. Gleichheit endet als eng gekoppelt mit Klassenhierarchien, die für spröde Code macht. Was mehr ist, wird eine Typprüfung vorgesehen, da diese Methoden nehmen nur Parameter vom Typ Object.

Es gibt einen Weg Gleichheit machen Überprüfung (und Hashing) viel weniger fehleranfällig und typsicher. In der Functional Java Bibliothek, finden Sie Equal<A> (und eine entsprechende Hash<A> ), wo Gleichheit in einer einzigen Klasse entkoppelt ist. Es verfügt über Methoden zum Zusammen Equal Instanzen für Ihre Klassen aus vorhandenen Instanzen sowie Wrapper für Sammlungen, Iterables , HashMap und HashSet , dass die Verwendung Equal<A> und Hash<A> statt equals und hashCode.

Was ist das Beste an diesem Ansatz ist, dass Sie nie equals und Hash-Methode schreiben vergessen können, wenn sie gefordert werden. Die Art System wird Ihnen helfen, erinnern.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top