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

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

Jan 08, 2021 am 10:22 AM
equals hashcode java

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 サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

ホットトピック

Excelは、動作しないことを見つけて交換します Excelは、動作しないことを見つけて交換します Aug 13, 2025 pm 04:49 PM

ChecksearchSettingslikeのように、「Matchentirecellcontents」および「Matchcase」byexpindedoptionsinfindandReplaceを使用して、「tocorrectscope内」内で「lookin "issettovaluesand」を保証します

Javaアプリケーションを展開する方法 Javaアプリケーションを展開する方法 Aug 17, 2025 am 12:56 AM

PrepareyourapplicationbyusingMavenorGradletobuildaJARorWARfile,externalizingconfiguration.2.Chooseadeploymentenvironment:runonbaremetal/VMwithjava-jarandsystemd,deployWARonTomcat,containerizewithDocker,orusecloudplatformslikeHeroku.3.Optionally,setup

Javaアプリケーションでロギングを構成する方法は? Javaアプリケーションでロギングを構成する方法は? Aug 15, 2025 am 11:50 AM

logbackまたはlog4j2と組み合わせたSLF4Jを使用することは、Javaアプリケーションでログを構成する推奨方法です。対応するMaven依存関係を追加することにより、APIおよび実装ライブラリを導入します。 2.コード内のSLF4JのLoggerFactoryを介してロガーを取得し、パラメーター化されたロギング方法を使用して分離した効率的なログコードを記述します。 3. logback.xmlまたはlog4j2.xml構成ファイルを介して、ログ出力形式、レベル、ターゲット(コンソール、ファイル)、およびパッケージレベルのログ制御を定義します。 4.オプションで、構成ファイルスキャン機能を有効にして、ログレベルの動的調整を実現し、スプリングブートをアクチュエータエンドポイントを介して管理することもできます。 5.を含むベストプラクティスに従ってください

JavaのCastorによるXMLデータバインディング JavaのCastorによるXMLデータバインディング Aug 15, 2025 am 03:43 AM

castorenablesxml-to-javaobjectmappingviadefault conventionsorexplicitmappingfiles;

JSは、配列の開始に要素を追加します JSは、配列の開始に要素を追加します Aug 14, 2025 am 11:51 AM

JavaScriptでは、配列の先頭に要素を追加する最も一般的な方法は、unshift()メソッドを使用することです。 1. unshift()を使用すると、元の配列が直接変更されると、1つ以上の要素を追加して、追加された配列の新しい長さを返すことができます。 2.元の配列を変更したくない場合は、拡張機能操作者([newElement、... arr]など)を使用して新しい配列を作成することをお勧めします。 3. concat()メソッドを使用して、新しい要素配列と元の番号を組み合わせて、元の配列を変更せずに新しい配列を返すこともできます。要約すると、元の配列を変更するときにunshift()を使用し、元の配列を変更しておくときは拡張オペレーターを推奨します。

パフォーマンスの比較:Java vs. Go for Backend Services パフォーマンスの比較:Java vs. Go for Backend Services Aug 14, 2025 pm 03:32 PM

gutypivityOffersbetterruntimeperformanceは、特にfori/o-heavyservices、duetoits lightgoroutinesineficientscheduler、whilejava、canslowertart、canmatchgoincpu-boundtasptimization.2.gouseslessme

JavaでJSONと協力する方法 JavaでJSONと協力する方法 Aug 14, 2025 pm 03:40 PM

Toworkwithjsoninjava、useathird-partylibrarylikejackson、gson、orjson-b、asjavalacksbuilt-insupport;

JavaのAssertキーワードは何ですか? JavaのAssertキーワードは何ですか? Aug 17, 2025 am 12:52 AM

theasertkeywordinjavaisusedtoeas sumptionsduringDevelopment、throwinganassertionerroriftheconditionispalse.2

See all articles