Post Page Advertisement [Top]


Objectクラスのマニュアルの中にhashCodeの一般的な規約は含まれております。


  • アプリケーション実行の途中、同じオブジェクトのhashCodeを複数呼び出す場合、equalsが使用する情報が変更されていないなら、いつも同じデータが返されなければならない。ただし、プログラムが終了され、再実行しても同じデータが返される必要はありません。
  • equals(Object)メソッドが同じだと判定した2つのオブジェクトhashCodeデータは同データでなければならない。
  • equals(Object)メソッドが異なると判定した2つのオブジェクトのhashCodeデータは必ず異なる必要はありません。しかし、別のhashCodeデータが返されたら、ハッシュテーブルのパフォーマンスが向上することは分かっていてください。



次のコードを見てください。簡単に作ってみたサンプルコードです。

public class Point {
    public static void main(String[] args){

        String a = "a1が";
        String b = "a1が";
        String sy = new String("b");
        String sx = new String("b");

        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        System.out.println(sy.hashCode());
        System.out.println(sx.hashCode());

        System.out.println("Pointxy :" + new Pointxy(1,2).hashCode());
        System.out.println("Pointxy :" + new Pointxy(1,2).hashCode());

        System.out.println("동일성 검사 :" + new Pointxy(1,2).equals(new Pointxy(1,2)));
    }

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

        @Override public boolean equals(Object o) {
            if (!(o instanceof Pointxy))
                return false;
            Pointxy p = (Pointxy) o;
            return p.x == x && p.y == y;
        }
    }
}
동일성 검사 = 同一性検査

hashCodeを作成する時、Stringオブジェクトはデータを

s[0]*31^(n-1)+ s[1]*31^(n-2)+...+ s[n-1]

このような一定の公式を出して作成します。

結果を見てください。もし別のオブジェクトであっても、
Stringのデータが同じ場合、HashCodeのデータは同じことが確認できます。

しかし、作成されたPointxyクラスの場合、内部フィールドのデータが同じでも、
別のデータが返されます。

最後の三つを確認すると、equalsはtrueですがhashCodeデータは違がうことが確認できます。

この場合、問題が生じます。

HashTableに入れた時、他のデータが返されたり、
nullが返されることになる可能性が高いです。

この問題を解決するため私たちは同じデータを扱う時、
同じhashCodeを返すようhashCodeをオーバーライドしてくれる必要があります。


最も簡単な方法は、この方式です。

@Override
public int hashCode() {
    return 42; // よくない方法です!
}



HashCodeは同じになりますが、すべてのオブジェクトのデータが同じになります。

この方法を使えば、全てのPointxyは全て同じバケットにHashされるので、
HashTableは、接続のリストになってしまいます。

そうなればリソースの浪費が免れるません。

ここはフィールドのデータを使って、効率的なHashCodeを作り出す方法が理想的です
そうすればリソースの負担も減らせることができます。

この本は完璧ではないが、完璧に近い方法を紹介しております。


1. 17のような0以外の定数をresultという名前のint型の変数に入れます。
2. オブジェクト内の全ての重要なフィールド 「f」 に対し(equalsメソッドが使用するフィールド)以下の手順を実施します。
A. フィールドのint HashCode「c」を計算します。
  1. フィールドがbooleanタイプなら(f?1:0)を計算します。
  2. フィールドがbyte、char、short、intのタイプなら(int)fを計算します。
  3. フィールドがlongタイプなら(int)(f ^(f >>> 32))を計算します。
  4. フィールドがfloatタイプならFloat.floatToIntBits(f)を計算します。
  5. フィールドがdoubleタイプならDouble.doubleToLongBits(f)を計算し、得られたlongデータを、上記のiii手順に従ってHashCodeに変換します。
  6. フィールドがオブジェクト変数で、equalsメソッドがそのフィールドのequalsメソッドを再帰的に呼び出す場合には、そのフィールドのhashCodeメソッドを再帰的に呼び出してHashCodeを計算します。より複雑に比べる必要がある場合には、そのフィールドの「代表形態」を計算し、代表形態についてhashCodeを呼び出します。フィールドのデータがnullの場合には、0を返します
  7. フィールドが配列の場合は、配列の各要素が別のフィールドのよう計算します。つまり、それぞれの重要要素に先ほど説明したルールを再帰的に適用してHashCodeを計算します。その後その結果を手順2.Bのように結合します。配列内のすべての要素が全部大事な場合は、JDK 1.5から提供されているArrays.hashCodeメソッドの一つを使ってください。
B. 上記の手順Aで計算されたハッシュコード 「c」 をresultに結合します。



3. resultを返します.
4. hashCode実装が終わったなら、ユニットテストを使い思うように結果が出るのかを確認してください。



上記の方法を基に直したしたPointxy内部クラスと、結果のデータです。

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

    @Override public boolean equals(Object o) {
        if (!(o instanceof Pointxy))
            return false;
        Pointxy p = (Pointxy) o;
        return p.x == x && p.y == y;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + x;
        result = 31 * result + y;
        return result;
    }
}



オブジェクトが違うがHashCodeも一致しするし、同一性検査もtrueが確認できます。

ここで性能を上げるために重要なフィールドをスキップする行動は控えてください。

そうすればHashの速度は速くなるかもしれないが、Hashデータのできが良くないので、HashTableの性能を落とすことになります。

댓글 없음:

댓글 쓰기

Bottom Ad [Post Page]

| Designed by Colorlib