codestory

Java System.identityHashCode, Object.hashCode und Object.equals verstehen

View more Tutorials:

1- Der Vertrag equals()

Die Methode equals(Object) wird verwendet, um das aktuelle Objekt mit einem anderen Objekt basierend auf den Werten der Eigenschaften jedes Objekts zu vergleichen. Sie können diese Methode in Ihrer Klasse überschreiben.
public boolean equals(Object other)
Beispiel: Die Klasse Money mit 2 Eigenschaften: currencyCode & amount. Zwei Objekte Money werden von der Methode equals() als gleich betrachtet, wenn sie denselben currencyCode und amount haben:
Money.java
package org.o7planning.equals.ex;

import java.util.Objects;

public class Money {
    private String currencyCode;
    private int amount;

    public Money(String currencyCode, int amount) {
        this.amount = amount;
        this.currencyCode = currencyCode;
    }

    public int getAmount() {
        return amount;
    }

    public String getCurrencyCode() {
        return currencyCode;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Money)) {
            return false;
        }
        Money o = (Money) other;
        return this.amount == o.amount //
                && Objects.equals(this.currencyCode, o.currencyCode);
    }
}
Wenn Sie die Methode equals() überschreiben, müssen Sie die folgenden Kriterien erfüllen, sie werden als Vertrag equals() bezeichnet:
1Reflexive
(Reflexiv)
Ein Objekt muss sich selbst entsprechen.
2Symmetric
(Symmetrisch)
x.equals(y) muss dasselbe Wert wie y.equals(x) zurückgeben.
3Transitive
(Transitiv)
Wenn x.equals(y) und y.equals(z) , dann auch x.equals(z).
4Consistent
(Konsistent)
Der Wert von x.equals(y) ändert sich nicht, wenn sich die am Vergleich beteiligten Property nicht ändern. (Zufälligkeit ist nicht erlaubt).

Symmetric

Die Symmetrie von equals() muss gewährleistet sein, dh wenn x.equals(y) dann y.equals(x) ist. Das klingt einfach, aber manchmal verletzt man es unbeabsichtigt.
Zum Beispiel verletzt die folgende Klasse WrongVoucher die Symmetrie des Vertrags equals():
WrongVoucher.java
package org.o7planning.equals.ex;

import java.util.Objects;

public class WrongVoucher extends Money {
    private String store;

    public WrongVoucher(String store, String currencyCode, int amount) {
        super(currencyCode, amount);
        this.store = store;
    }

    public String getStore() {
        return store;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof WrongVoucher)) {
            return false;
        }
        WrongVoucher o = (WrongVoucher) other;
        
        return this.getAmount() == o.getAmount() //
                && Objects.equals(this.getCurrencyCode(), o.getCurrencyCode()) //
                && Objects.equals(this.store, o.store);
    }
}
Auf den ersten Blick scheinen die Klasse WrongVoucher und ihre Methode equals() korrekt zu sein. Es funktioniert perfekt, wenn Sie 2 Objekte WrongVoucher vergleichen, aber Sie werden Probleme sehen, wenn Sie Objekte WrongVoucher mit Objekte Money vergleichen und umgekehrt.
WrongVoucherTest.java
package org.o7planning.equals.ex;

public class WrongVoucherTest {
    
    public static void main(String[] args)  {
        Money m = new Money("USD", 100);
        WrongVoucher wv = new WrongVoucher("Chicago S1", "USD", 100);
        
        System.out.println("m.equals(wv): " + m.equals(wv)); // true
        System.out.println("wv.equals(m): " + wv.equals(m)); // false
    }
}
Output:
m.equals(wv): true
wv.equals(m): false
Um die obige Falle zu vermeiden, können wir die Klasse Voucher neu schreiben und Money als Property verwenden, anstatt von Money zu erben.
Voucher.java
package org.o7planning.equals.ex;

import java.util.Objects;

public class Voucher {
    private String store;
    private Money money;

    public Voucher(String store, String currencyCode, int amount) {
        this.store = store;
        this.money = new Money(currencyCode, amount);
    }

    public String getStore() {
        return store;
    }

    public Money getMoney() {
        return money;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Voucher)) {
            return false;
        }
        Voucher o = (Voucher) other;
        
        return Objects.equals(this.store, o.store) //
                && this.money.equals(o.money);
    }
}

2- System.identityHashCode(Object)

In Java gibt die statische Methode System.identityHashCode(obj) den identity hashcode des Objekts obj zurück, der eine nicht negative Integer zwischen [0, 2^31-1] ist. Der Identity hashcode eines Objekt null ist 0.
@HotSpotIntrinsicCandidate
public static native int identityHashCode(Object x);
Gemäß der Designidee sollte der identity hashcode verschiedener Objekte unterschiedlich sein. Dies ist jedoch nicht absolut garantiert. Der Algorithmus von JVM kann nur garantieren, dass die Wahrscheinlichkeit eines doppelten identity hashcode sehr gering ist. Der identity hashcode eines Objekts wird nur im ersten Moment der tatsächlichen Verwendung berechnet und im Header des Objekts gespeichert.
Identity hashcode wird sicherlich nicht basierend auf der Adresse des Objekts im Speicher generiert. Leider gibt es keine Dokumentation über den Generierungsalgorithmus für Identity hashcode. Das Geheimnis liegt im Quellcode der JVM, der in C++ geschrieben wurde. Ich werde diesen Algorithmus aktualisieren, wenn weitere Informationen verfügbar sind.

3- Object.hashcode()

Die Methode hashCode() der Klasse java.lang.Object gibt den hashcode des aktuellen Objekts zurück, der genau der identity hashcode dieses Objekts ist.
public class Object  {
    
    public int hashCode() {
        return System.identityHashCode(this);
    }
}
Beispiel für hashcode und identity hashcode eines reinen Objekts (new java.lang.Object()).
HashCodeEx1.java
package org.o7planning.hashcode.ex;

public class HashCodeEx1 {

    public static void main(String[] args) {
        Object obj1 = new Object();
        
        int idHashcode = System.identityHashCode(obj1);
        int hashcode = obj1.hashCode();
        
        System.out.println("Identity Hashcode: " + idHashcode);
        System.out.println("Hashcode: " + hashcode);
    }
}
Output:
Identity Hashcode: 1651191114
Hashcode: 1651191114
Nachkommende Klassen von java.lang.Object können die Methode hashCode() überschreiben, um einen benutzerdefinierten Wert zurückzugeben, müssen jedoch die folgenden Regeln sicherstellen, die auch als Vertrag hashCode() bezeichnet werden.
1Equals consistency
Wenn zwei Objekte gemäß der Methode equals(Object) gleich sind, muss ihre Methode hashCode() denselben Wert zurückgeben.
2Internal consistency
Der Wert von hashCode() kann sich nur ändern, wenn sich die an der Methode equals(Object) beteiligten Eigenschaften ändern.
Zwei Objekte, die gemäß der Methode equals(Object) nicht gleich sind, haben nicht unbedingt unterschiedliche hashcode. Zwei verschiedene Objekte mit unterschiedlichen Werten hashcode verbessern jedoch die Leistung der Hash table (Weitere Erläuterungen finden Sie im Artikel über HashMap und HashSet).
mehr sehen:
  • TODO Link?
HashCodeEx2.java
package org.o7planning.hashcode.ex;

public class HashCodeEx2 {

    public static void main(String[] args) {
        Employee tom = new Employee("Tom");
        Employee jerry = new Employee("Jerry");
        
        System.out.println("Employee: " + tom.getFullName());
        System.out.println("  - Identity hashcode: " + System.identityHashCode(tom));
        System.out.println("  - Hashcode: " + tom.hashCode());
        
        System.out.println("\nEmployee: " + jerry.getFullName());
        System.out.println("  - Identity hashcode: " + System.identityHashCode(jerry));
        System.out.println("  - Hashcode: " + jerry.hashCode());
    }
}

class Employee {
    private String fullName;

    public Employee(String fullName) {
        this.fullName = fullName;
    }
    
    public String getFullName()  {
        return this.fullName;    
    }

    @Override
    public int hashCode() {
        if (this.fullName == null || this.fullName.isEmpty()) {
            return 0;
        }
        char ch = this.fullName.charAt(0);
        return (int) ch;
    }
}
Output:
Employee: Tom
  - Identity hashcode: 1579572132
  - Hashcode: 84

Employee: Jerry
  - Identity hashcode: 359023572
  - Hashcode: 74

4- Die Konsistenz hashCode() & equals() verletzen

Grundsätzlich müssen Sie, wenn Ihre Klasse die Methode equals(Object) überschreibt, auch die Methode hashCode() überschreiben, um sicherzustellen, dass 2 Objekte, die nach der Methode equals(Object) gleich sind, denselben hashcode haben. Dies ist notwendig und sicher, wenn Sie das Objekt dieser Klasse als Schlüssel von *HashMap (HashMap, WeakHashMap, IdentityHashMap,...) verwenden.
Die folgende Klasse BadTeam verletzt Equals consistency (Gleichheitskonsistenz):
BadTeam.java
package org.o7planning.equals.ex;

import java.util.Objects;

public class BadTeam {
    private String name;
    private int numberOfMembers;
    
    public BadTeam(String name, int numberOfMembers) {
        this.name = name;
        this.numberOfMembers = numberOfMembers;
    }
    public String getName() {
        return name;
    }
    public int getNumberOfMembers() {
        return numberOfMembers;
    }
    
    @Override
    public boolean equals(Object other)  {
        if(this == other)  {
            return true;
        }
        if(!(other instanceof BadTeam))  {
            return false;
        }
        BadTeam o = (BadTeam) other;
        return Objects.equals(this.name, o.name);
    }
    
    @Override
    public int hashCode()  {
        return this.numberOfMembers;
    }
}
BadTeamTest.java
package org.o7planning.equals.ex;

public class BadTeamTest {

    public static void main(String[] args) {
        BadTeam team1 = new BadTeam("Team 1", 3);
        BadTeam team2 = new BadTeam("Team 1", 5);
        
        boolean isEquals = team1.equals(team2); // true
        
        int hashcode1 = team1.hashCode(); // 3
        int hashcode2 = team2.hashCode(); // 5
        
        System.out.println("team1.equals(team2): " + isEquals); // true
        System.out.println("hashcode1 == hashcode2: " + (hashcode1 == hashcode2)); // false
    }
}
Output:
team1.equals(team2): true
hashcode1 == hashcode2: false
Wenn Sie die Klasse *HashMap (HashMap, WeakHashMap, IdentityHashMap,..) verwenden, kann dies zu einer Verletzung des Vertrag hashCode() führen. Die Dinge funktionieren möglicherweise nicht so, wie Sie es erwarten.
HashMap_BadTeam_Test.java
package org.o7planning.equals.ex;

import java.util.HashMap;

public class HashMap_BadTeam_Test {

    public static void main(String[] args) {

        // BadTeam team --> String leader.
        HashMap<BadTeam, String> map = new HashMap<>();

        BadTeam team1 = new BadTeam("Team 1", 3);
        BadTeam team2 = new BadTeam("Team 1", 5);
        
        map.put(team1, "Tom");
        map.put(team2, "Jerry");
        
        BadTeam team = new BadTeam("Team 1", 10);
        
        String leader = map.get(team);
        System.out.println("Leader of " + team.getName() + " is " + leader);
    }
}
Output:
Leader of Team 1 is null
Sehen Sie sich auch an, wie HashMap, WeakHashMap und IdentityHashMap dieDaten speichern, um mehr zu verstehen, was oben erwähnt wurde:

View more Tutorials:

Vielleicht bist du interessiert

Das sind die Online-Courses außer der Website o7planning, die wir empfehlen. Sie können umsonst sein oder Discount haben.