メモ用のブログ

技術系のメモとかを書いておきます

オブジェクトのハッシュコードについて

超初歩的な事かもですが…。

Javaで配列オブジェクトをSystem.out.println()等で表示した時の結果についてメモです。

  • サンプルコード
public class Sample {
    public static void main(String[] args) {
        String[] str = new String[10];
        System.out.println("string[]:" + str);
    }
}
  • 実行結果
string[]:[Ljava.lang.String;@3e25a5

クラス名とハッシュコードが表示されます。

C言語のノリで、なんとなくアドレスだと思っていました。。。
Javaはアドレスを意識しないで良いのが利点なのに、アドレスが表示されるわけがないですよね。
と言ってもアドレスと無関係ではないみたいですが。。。

☆ハッシュコードとは

Objectクラスで定義されているhashCode()メソッドを使って取得できる整数値のこと。
→全てのクラスでハッシュコードを取得できる(Objectクラスは全てのクラスの親クラスのため)
通常はオブジェクトの内部アドレスを整数値に変換する形で実装されています。
また、一度処理を終了した後に再度処理を実行した場合、hashCode()の値は前回の値と等しくなるとは限りません。

  • サンプルコード
public class Sample {
    public static void main(String[] args) {
        String[] str = new String[10];
        System.out.println("string[]:" + str);
        System.out.println("str hash:" + 
            Integer.toHexString(str.hashCode()));
    }
}
  • 実行結果
string[]:[Ljava.lang.String;@3e25a5
str hash:3e25a5

オブジェクトをSystem.out.println()で表示した場合は下記の値と等しくなります。

// クラス名+@+16進数で表示されるハッシュコード
getClass().getName() + '@' + Integer.toHexString(hashCode())
equals()メソッドとの関係

二つのオブジェクトがequals()メソッドで等価と判断された場合、それぞれhaseCode()メソッドの返り値は等しくならなければいけません。ただし、逆(equals()メソッドで等しくないと判断された場合)は異なる値を返す必要はありません。

  • サンプルコード
    • Sampleクラス
public class Sample {
    public static void main(String[] args) {
        Hoge num1 = new Hoge(1);
        Hoge num2 = new Hoge(1);
        Hoge num3 = num1;
		
        System.out.println("str num1:" + 
            Integer.toHexString(num1.hashCode()));
        System.out.println("str num2:" + 
            Integer.toHexString(num2.hashCode()));
        System.out.println("str num3:" + 
            Integer.toHexString(num3.hashCode()));
		
        if (num1.equals(num2)) {
            System.out.println("num1 equals num2.");
        } else {
            System.out.println("num1 not equals num2.");
        }
		
        if (num1.equals(num3)) {
            System.out.println("num1 equals num3.");
        } else {
            System.out.println("num1 not equals num3.");
        }
    }
}
public class Hoge {
    public int num;
    public Hoge (int val) {
        num = val;
    }
}
  • 実行結果
num1 hash:19821f
num2 hash:addbf1
num3 hash:19821f
num1 not equals num2.
num1 equals num3.

インスタンス変数の値が同じであっても、num1とnum2はオブジェクトの実体が異なるため、equals()メソッドでは等しくないと判断されます。ですが、num3はnum1を代入しただけなので、num1もnum3も同じ実体を指しています。そのためequals()メソッドでは等価と判断され、hashCode()メソッドの返り値も等しくなります。
また、num1とnum2を等しいと判断するようにequals()メソッドをオーバライドする場合は、hashCode()メソッドもオーバライドする必要があります。


一部のクラスではequals()メソッド、hashCode()メソッドがオーバライドされています。
下記はStringクラスのequals()メソッドとhashCode()メソッドのソースコードです。

  • Stringクラス
    • equalメソッド
public boolean equals(Object anObject) {
    //実体が等価の場合はtrue
    if (this == anObject) {
        return true;
    }
    //String型のオブジェクトかどうか調べる
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        //文字数が等しいかどうか
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            //1文字ずつチェック
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                //不一致の場合false
                return false;
            }
            //同じ文字列ならtrue
            return true;
        }
    }
    return false;
}
    • hashCodeメソッド
public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;
        //ハッシュコードの作成
        //計算式:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
        for (int i = 0; i < len; i++) {
            h = 31*h + val[off++];
        }
        hash = h;
    }
    return h;
}

Stringクラスのequals()メソッドではオブジェクトの実体の比較ではなく、内容の比較を行っています。またhashCode()メソッドでは、文字と文字列の長さを使ってハッシュコードの計算を行っているため、内容の文字列が等しい場合ハッシュコードも等しくなります。

それでは、Stringクラスのequals()メソッドとhashCode()メソッドの動きをサンプルコードで見てみます。

  • サンプルコード
    • Sampleクラス
public class Sample {
    public static void main(String[] args) {
        String str1 = new String("a");
        String str2 = new String("a");
        String str3 = str1;

        System.out.println("--ハッシュコード--");
        System.out.println("str1 hash:" + 
            Integer.toHexString(str1.hashCode()));
        System.out.println("str2 hash:" + 
            Integer.toHexString(str2.hashCode()));
        System.out.println("str3 hash:" + 
            Integer.toHexString(str3.hashCode()));

        System.out.println("--比較結果--");		
        if (str1.equals(str2)) {
            System.out.println("str1 equals str2.");
        } else {
            System.out.println("str1 not equals str2.");
        }
        if (str1.equals(str3)) {
            System.out.println("str1 equals str3.");
        } else {
            System.out.println("str1 not equals str3.");
        }
        //元のハッシュコードを参照
        System.out.println("--元のハッシュコード--");
        System.out.println("str1 id_hash:" +
            Integer.toHexString(
                System.identityHashCode(str1)));
        System.out.println("str2 id_hash:" +
            Integer.toHexString(
                System.identityHashCode(str2)));
        System.out.println("str3 id_hash:" + 
            Integer.toHexString(
                System.identityHashCode(str3)));
    }
}
  • 実行結果
--ハッシュコード--
str1 hash:61
str2 hash:61
str3 hash:61
--比較結果--
str1 equals str2.
str1 equals str3.
--元のハッシュコード--
str1 id_hash:3e25a5
str2 id_hash:19821f
str3 id_hash:3e25a5

str1とstr2は実体は異なりますが、内容が等しいのでequals()メソッドでは等価と判断されます。hashCode()メソッドでも、同じ値が返っています。
もちろん、str1とstr3は実体は同じなので等しいと判断されます。
ここで元のハッシュコードを見てみると、str1とstr3は等しくstr2は異なる値になっていることが分かります。
(identityHashCode()メソッドでは、デフォルトのhashCode()メソッドで取得されるハッシュコードを返します。)


ここまでアレコレ書いておいてなんですが。。。

できる限り、Object クラスで定義される hashCode メソッドは、異なるオブジェクトについては異なる整数値を返します。通常、これはオブジェクトの内部アドレスを整数値に変換する形で実装されますが、そのような実装テクニックは JavaTM プログラミング言語では不要です。

http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/lang/Object.html#hashCode%28%29

あまりhashCodeを意識するようなコーディングはよろしくないようです。
知識程度に覚えておいた方が良いという事ですね。