ホームページ  >  記事  >  Java  >  Java は、equals を書き換えるときにハッシュコードを書き換える必要があるのはなぜですか?

Java は、equals を書き換えるときにハッシュコードを書き換える必要があるのはなぜですか?

王林
王林転載
2021-01-08 10:22:332699ブラウズ

Java は、equals を書き換えるときにハッシュコードを書き換える必要があるのはなぜですか?

最初に結論を言います:

まず、イコールの書き換えには必ずしもハッシュコードが必要ではなく、実際の状況によって異なることを明確にしておく必要があります。例えば、コンテナを使用しない場合は必要ありませんが、HashMapなどのコンテナを使用し、Keyとしてカスタムオブジェクトを使用する場合は書き換えが必要になります。

(学習ビデオ共有: java ビデオ チュートリアル)

等しいの書き換えとは、ビジネス ロジックにおいてインスタンスが等しいかどうかを判断することです。 hascode を書き換える目的は、コレクションの重みを迅速に決定することです。

hashCode() とquals() の規則:

1. 2 つのオブジェクトが等しい場合、ハッシュコードも同じである必要があります
2. 2 つのオブジェクトは、2 つの場合に等しいequals() メソッドは true
3 を返します。2 つのオブジェクトは同じハッシュコード値を持ち、必ずしも等しいわけではありません
4。要約すると、equals() メソッドがオーバーライドされている場合、hashCode() メソッドも同様にする必要があります。 be Override
5. hashCode() のデフォルトの動作は、ヒープ上のオブジェクトに対して一意の値を生成することです。 hashCode() がオーバーライドされない場合、このクラスの 2 つのオブジェクトは等しくありません (2 つのオブジェクトが同じデータを指している場合でも)。

次の例は、書き換えの必要性を示しています。

HashMap のキーとしてカスタムクラスを使用して配置する場合

equals だけを書き換えて hashCode を書き換えない場合、ロジックエラーが発生します

最初に次のコードを見てください

public class Test {

    static class Order {
    
        private Long orderId;

        public Order(Long orderId) {
            this.orderId = orderId;
        }

        public Long getOrderId() {
            return orderId;
        }

        public void setOrderId(Long orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj != null && !(obj instanceof Order)) {
                return false;
            }

            return Objects.equals(this.orderId, ((Order) obj).orderId);
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    '}';
        }
    }

    public static void main(String[] args) {
        Map<Order, String> map = new HashMap<>();

        Order order1 = new Order(1000000001L);
        Order order2 = new Order(1000000001L);

        map.put(order1, "");
        map.put(order2, "");

        System.out.println(map);
    }
}

実行出力:

{Order{orderId=1000000001}=, Order{orderId=1000000001}=}

equals メソッドはコード内で書き換えられますが、hashCode メソッドは書き換えられません。
等しい書き換えのロジックは次のとおりです。orderId が等しい限り、2 つのオブジェクトは等しいです。
実行結果から判断すると、同じ orderId を持つ 2 つのオブジェクトがマップに正常に配置されました。論理的に予期される結果はマップ内にオーダーが 1 つだけ存在するはずであるため、これは論理エラーです。
HashMap のソース コードを見てみましょう
コメント付きの判定を見てみましょう

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 通过hash算出索引  通过索引取值==null的话  直接直接插入到索引位置。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

ソース コードから、ハッシュ コードが異なる限り、直接問題が発生する可能性があることがわかります。配列に挿入されます。ただし、hashCode メソッドをオーバーライドしていないため、Object の hashCode メソッドが呼び出されます。 Object の hashCode は、ヒープ内のオブジェクトのアドレスを使用してアルゴリズムを通じて int 型の値を導出しますが、この場合、作成した 2 つのオブジェクトの int 型の値は異なる必要があるため、両方の Order を同じにすることができます。配列に正常に挿入されたため、論理エラーが発生しました。

hashCode メソッドを書き換えます:

public class TestHash {

    static class Order {


        private Long orderId;

        public Order(Long orderId) {
            this.orderId = orderId;
        }

        public Long getOrderId() {
            return orderId;
        }

        public void setOrderId(Long orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj != null && !(obj instanceof Order)) {
                return false;
            }

            return Objects.equals(this.orderId, ((Order) obj).orderId);
        }

        @Override
        public int hashCode() {
        	// 这里简单重写下   实际开发根据自己需求重写即可。
            return this.orderId.intValue() >> 2;
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    &#39;}&#39;;
        }
    }

    public static void main(String[] args) {
        Map<Order, String> map = new HashMap<>();

        Order order1 = new Order(1000000001L);
        Order order2 = new Order(1000000001L);

        map.put(order1, "");
        map.put(order2, "");

        System.out.println(map);
    }
}

出力を再度実行します:

{Order{orderId=1000000001}=}

ソース コードを簡単に見てみましょう (理解を深めるために、キー コードのみをインターセプトしました) ): Put order2 コメントとして説明されています。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 重写hashCode之后两个对象的orderId相同,hashCode也肯定相同。
    // 通过hash算出索引  通过索引取值  有值不进入if。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 由于重写了hashCode  旧对象的hashCode和新的肯定相等
        if (p.hash == hash &&
        // (k = p.key) == key == false 因为比较的是对象地址
        // (key != null && key.equals(k)) == true 因为重写了equals orderId相等则相等 
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 保存旧Node
            e = p;
        .......
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
            	// value覆盖旧Node的值
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
   ........
}

したがって、注文 2 は注文 1 をカバーします。このため、カスタム オブジェクトを HashMap のキーとして使用する場合、equals をオーバーライドする場合は hashCode も必要になります。

逆に言うと、hashCodeを書き換えた後、equalsを書き直す必要があるのでしょうか?

答えは「はい」です。すべて書き直す必要があります。

上記のコードを書き換えるロジックを例に挙げます。同じ hashCode を持つ 2 つのオブジェクトがあり、order1 が put されたとします。put するとき、ハッシュは同じで、結果のインデックスは次のようになります。も同じであればorder1が得られる 以降は引き続きequalsで比較する 書き換えが無いとしてオブジェクトアドレス比較となる 結果はfalseになるはず この時ハッシュ衝突が発生する、リンクされたリストが形成されます。

また、map.get(key)では、hashCodeに基づいて検索され、等しいかどうかが判断されます。
なぜ平等に判断する必要があるのでしょうか? hashCode に基づいて検索されるのはリンク リストであるため、equals に基づいてリンク リスト内で等しい Key を持つ値を検索する必要があります。

カスタム クラスをキーとして使用するシナリオは何ですか?

最も一般的なキーは座標です。たとえば、マップ上の特定の座標にオブジェクトを配置します。

public class Test {

    static class Coordinate {
        public Coordinate(int x, int y) {
            this.x = x;
            this.y = y;
        }

        private int x;
        private int y;

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }
    }

    public static void main(String[] args) {
        Map<Coordinate, String> map = new HashMap<>();
        map.put(new Coordinate(22, 99), "手机");
        map.put(new Coordinate(44, 48), "电脑");
    }
}

関連する推奨事項: Java 入門チュートリアル

以上がJava は、equals を書き換えるときにハッシュコードを書き換える必要があるのはなぜですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。