Developer

【はじめてのJava】hashCode()【いろいろなクラス編】
2022.01.31
Lv1

【はじめてのJava】hashCode()【いろいろなクラス編】

はじめてのJava

このシリーズでは、初めてJavaやプログラミングを勉強する方向けに、Javaによるプログラミングの基礎を説明していきます。
目標レベルは、Javaの資格試験の一つである「Oracle Certified Java Programmer, Silver」(通称Java Silver)に合格できる程度の知識の習得です。
はじめてJavaやプログラムに触れる方にもできるだけわかりやすい解説を心がけていきます。


目次


Objectクラス

Objectクラスは、Javaクラス構造を構成するルートとなるクラスです。すべてのクラスはObjectクラスの直接または間接的にこのクラスから派生しています。
また、私たちが独自に定義したクラスも暗黙的にObjectクラスを継承しています。
したがって、すべてのクラスがObjectクラスで定義されているメソッド群を継承していることになります。

今シリーズの記事では、このObjectクラスで定義されているメソッドのうち、toString(),equals(),hashCode()について3記事にわたって紹介します。


hashCode()

hashCode()メソッドは、呼び出し元のオブジェクトをハッシュコードと呼ばれるint型の値に変換し、戻り値として返すメソッドです。
ハッシュコードとはオブジェクトにつけられる一つの整数値で、オブジェクトのユニークな識別や、データ構造体へのオブジェクトの格納と取得を簡単化するために用いられる値です。
ハッシュコードは主にHashMapのキーなどで使われます。

hashCode()はObjectクラスで定義されているメソッドで、独自に定義せずとも全てのクラスでハッシュコードを計算することが出来ます。また、hashCode()メソッドをオーバーライドしてハッシュコード計算用のアルゴリズムを実装することも可能ですが、その際にはハッシュコード要件と呼ばれる、ハッシュコードの取得に関する次のルールを守る必要があります。

・あるアプリケーションを一度実行し始めたら、同一オブジェクトに対してhashCode()メソッドが複数回呼び出された場合、いずれの戻り値も同一の整数値を返す必要がある。ただし、同じアプリケーションであってもプログラムを再実行時した際には、返される整数値は異なっていてもよい

・2つのオブジェクトがequals()メソッドで同一と判定される場合、これら2つのオブジェクトに対するhashCode()メソッドの実行結果は、同一の整数をハッシュコードとして返す必要がある

・2つのオブジェクトがequals()メソッドで同一と判定されない場合、これら2つのオブジェクトに対するhashCode()メソッドの実行結果は、必ずしも異なる整数をハッシュコードとして返す必要はない。ただし、同一でないオブジェクトに対して異なるハッシュコードが返されると、ハッシュテーブルのパフォーマンスが向上する場合があるので、その点は考慮する必要がある

2つ目の要件に注目してください。equalsメソッドでtrueが返ってくる場合、それら2つのオブジェクトは同じハッシュコードを返す必要があります。
これはequals()メソッドを独自の比較でオーバーライドしたクラスは、それに合わせてhashCode()もオーバーライドして書き換える必要があるということを表しています。
equals()メソッドのみオーバーライドしてhashCode()メソッドをオーバーライドしなかった場合、equals()メソッドによってtrueを返す二つのオブジェクトが異なるハッシュコードを返してしまうという状態を引き起こしてしまいます。

独自のクラスでequals()メソッドをオーバーライドする場合は、hashCode()メソッドも併せてオーバライドするようにしましょう。
※厳密には「equals()メソッドがtrueになる⇒hashCode()メソッドの値が同一になる」と「hashCode()メソッドの値が異なる⇒equals()メソッドがfalseになる」の2つが成り立つように「equals()メソッド」「hashCode()メソッド」の2つが実装されている必要があります。詳しくは割愛します。

それでは、hashCode()メソッドに関するサンプルコードを見てみましょう。

package blog.hashcode;

public class Sample {
	public static void main(String[] args) {
        HashStore hs1 = new HashStore(89, 101);
        HashStore hs2 = new HashStore(75, 101);
        HashStore hs3 = new HashStore(89, 101);

        System.out.println("Hashcode for hs1: " + hs1.hashCode());
        System.out.println("Hashcode for hs2: " + hs2.hashCode());
        System.out.println("Hashcode for hs3: " + hs3.hashCode());

        if (hs1.equals(hs2)) {
            System.out.println("hs1 is equal to hs2");
        } else {
            System.out.println("hs1 is not equal to hs2");
        }

        if (hs1.equals(hs3)) {
            System.out.println("hs1 is equal to hs3");
        } else {
            System.out.println("hs1 is not equal to hs3");
        }
    }

}
class HashStore {
    private int key;
    private int value;
    private int storeSize = 10;

    HashStore(int key, int value) {
        this.key = key;
        this.value = value;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof HashStore)) {
            return false;
        }
        HashStore hs = (HashStore) obj;
        return (key == hs.key && value == hs.value);
    }

    public int hashCode() {
        return key % storeSize;
    }
}


サンプルコードではHashStoreという独自のクラスを定義し、hs1,hs2,hs3という三つのオブジェクトをインスタンス化して比較しています。
また、HashStoreequals()メソッドとhashCode()メソッドをオーバーライドして実装しており、ハッシュコード要件をきちんと満たしています。
例えば、hs1とhs3はequals()メソッドによる比較でtrueを返しています。そして、hs1とhs3がhashCode()メソッドで返すハッシュコードは9で同じ値を返していることが確認できます。これはハッシュコード要件の二つ目の要件を満たしていることを表しています。

ハッシュコードの注意点として、2つのオブジェクトが同一である場合、両者のハッシュコードも同一の値を返す必要がありますが、その逆に関しては求められていないというものがあります。
たとえば、サンプルコードのHashStoreのオブジェクトで、返すハッシュコードが9だったとしても、hs1やhs3と同一のオブジェクトであるとは限らないということです。


ポイント

・hashCode()メソッドはMapインタフェースのKeyなどの用途で利用されるオブジェクトでは適切に実装されている必要がある。
・equals()メソッドとhashCode()をオーバーライドする際には、両方を適切にオーバーライドする必要がある。
・同じハッシュコードを返すオブジェクトが同一のオブジェクトであるとは限らない。


はじめてのJavaシリーズの目次はこちら