Javaでのhashcodeメソッド


一.hashCodeメソッドの役割
コンテナタイプを含むプログラム設計言語では、基本的にhashCodeに関連します.Javaにおいても、hashCodeメソッドの主な役割は、HashSet、HashMap、およびHashTableを含むハッシュベースのセットとともに正常に動作することである.
どうしてそう言うのですか.1つのケースを考えて,集合にオブジェクトを挿入すると,集合にそのオブジェクトが既に存在するか否かをどのように判別するか.(注:コレクションに重複する要素は許可されていません)
equalsメソッドを呼び出して1つずつ比較することを多くの人が考えているかもしれませんが、このメソッドは確かに実行できます.しかし,集合にすでに1万個以上のデータが存在する場合,equals法を用いて逐一比較すると,効率は必然的に問題となる.このときhashCodeメソッドの役割は,集合が新しいオブジェクトを追加しようとすると,まずこのオブジェクトのhashCodeメソッドを呼び出し,対応するhashcode値を得ることであるが,実際にはHashMapの具体的な実装では既に格納されているオブジェクトのhashcode値を1つのtableで保存し,tableにこのhashcode値がなければ直接格納することができ,これ以上比較する必要はありません.このhashcode値が存在する場合、そのequalsメソッドを呼び出して新しい要素と比較し、同じであれば保存せず、異なるであれば他のアドレスをハッシュするので、ここでは衝突解決の問題があり、これにより実際にequalsメソッドを呼び出す回数が大幅に減少し、一般的に言えば、JavaのhashCodeメソッドは、オブジェクトに関連する情報(オブジェクトのストレージアドレス、オブジェクトのフィールドなど)を一定のルールに基づいてハッシュ値としてマッピングすることです.
  
そのため、hashcode値に基づいて2つのオブジェクトが等しいかどうかを直接判断できるという人もいますか?異なるオブジェクトで同じhashcode値が生成される可能性があるため、できないに違いありません.hashcode値に基づいて2つのオブジェクトが等しいか否かを判断することはできないが、直接hashcode値に基づいて2つのオブジェクトが等しくないと判断することができ、2つのオブジェクトのhashcode値が等しくなければ、必ず2つの異なるオブジェクトである.2つのオブジェクトが本当に等しいかどうかを判断するには、equalsメソッドを使用する必要があります.
すなわち、equalsメソッドを呼び出した結果がtrueである場合、2つのオブジェクトのhashcode値は必ず等しい.
equalsメソッドで得られた結果がfalseである場合、2つのオブジェクトのhashcode値は必ずしも異なるとは限らない.
2つのオブジェクトのhashcode値が等しくない場合、equalsメソッドで得られた結果は必ずfalseである.
2つのオブジェクトのhashcode値が等しい場合、equalsメソッドで得られた結果は不明です.
二.equalsメソッドとhashCodeメソッド
場合によっては、プログラミング者がクラスを設計する際にStringクラスのようなequalsメソッドを書き換える必要があるが、equalsメソッドを書き換えると同時にhashCodeメソッドを書き換える必要があることに注意してください.どうしてそう言うのですか.
package com.cxh.test1;

 

import java.util.HashMap;

import java.util.HashSet;

import java.util.Set;

 

 

class People{

    private String name;

    private int age;

     

    public People(String name,int age) {

        this.name = name;

        this.age = age;

    }  

     

    public void setAge(int age){

        this.age = age;

    }

         

    @Override

    public boolean equals(Object obj) {

        // TODO Auto-generated method stub

        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;

    }

}

 

public class Main {

 

    public static void main(String[] args) {

         

        People p1 = new People("Jack", 12);

        System.out.println(p1.hashCode());

             

        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();

        hashMap.put(p1, 1);

         

        System.out.println(hashMap.get(new People("Jack", 12)));

    }

}

ここではequalsメソッドだけを書き直しました.つまり、2つのPeopleオブジェクトが、名前と年齢が等しい場合は、同じ人だと思います.
このコードは本来、このコードの出力結果が「1」であることを意図しているが、実際には「null」を出力している.どうしてですか.なぜならequalsメソッドを書き換えると同時にhashCodeメソッドを書き換えることを忘れてしまうからである.
equalsメソッドを書き換えることにより、論理的に名前と年齢が同じ2つのオブジェクトが等しいオブジェクト(Stringクラスと類似)と判定されるが、デフォルトではhashCodeメソッドはオブジェクトの格納アドレスをマッピングすることであることがわかる.上記のコードの出力結果が「null」であることは珍しくない.理由は簡単で、p 1が指すオブジェクトと
  System.out.println(hashMap.get(new People("Jack", 12)));この文のnew People(「Jack」,12)は2つのオブジェクトを生成し,それらの記憶アドレスは異なるに違いない.
 
HashMapのgetメソッドの具体的な実装:
public V get(Object key) {

        if (key == null)

            return getForNullKey();

        int hash = hash(key.hashCode());

        for (Entry<K,V> e = table[indexFor(hash, table.length)];

             e != null;

             e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

                return e.value;

        }

        return null;

    }

したがってhashmapでget操作を行う場合、得られるhashcdoe値が異なるため(上記のコードは場合によっては同じhashcode値が得られるかもしれないが、この確率は比較的小さく、2つのオブジェクトの記憶アドレスが異なる場合でも同じhashcode値が得られる可能性があるため)、getメソッドではforループが実行されずnullに戻る.
したがって,上記のコード出力結果を「1」とするのは簡単で,equalsメソッドとhashCodeメソッドが常に論理的に一致するようにhashCodeメソッドを書き換えるだけである.
 
package com.cxh.test1;

 

import java.util.HashMap;

import java.util.HashSet;

import java.util.Set;

 

 

class People{

    private String name;

    private int age;

     

    public People(String name,int age) {

        this.name = name;

        this.age = age;

    }  

     

    public void setAge(int age){

        this.age = age;

    }

     

    @Override

    public int hashCode() {

        // TODO Auto-generated method stub

        return name.hashCode()*37+age;

    }

     

    @Override

    public boolean equals(Object obj) {

        // TODO Auto-generated method stub

        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;

    }

}

 

public class Main {

 

    public static void main(String[] args) {

         

        People p1 = new People("Jack", 12);

        System.out.println(p1.hashCode());

             

        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();

        hashMap.put(p1, 1);

         

        System.out.println(hashMap.get(new People("Jack", 12)));

    }

}

これで出力結果は「1」になります.
 
  • プログラム実行中、equalsメソッドの比較操作で使用される情報が変更されていない限り、同じオブジェクトに対して複数回呼び出され、hashCodeメソッドは常に同じ整数を返さなければならない.
  • 2 equalsメソッドに従って2つのオブジェクトが等しい場合、2つのオブジェクトを呼び出すhashCodeメソッドは、同じ整数結果を返さなければなりません.
  • 両オブジェクトがequalsメソッドに従って比較されている場合、hashCodeメソッドは必ずしも異なる整数を返さなければならないとは限らない.

  • 第2条と第3条についてはよく理解できますが、第1条は、無視することが多いです.『Javaプログラミング思想』という本のP 495ページにも、第1条と似たような話があります.
    「hashCode()を設計する上で最も重要な要因は、同じオブジェクトに対してhashCode()を呼び出す場合、いつでも同じ値を生成することです.HashMapにオブジェクトをput()で追加する場合、get()でhashCdoe値を生成します.取り出したときに別のhashCode値が生成されると、そのオブジェクトを取得できなくなります.したがって、hashCodeメソッドがオブジェクトの変化しやすいデータに依存している場合は、ユーザーは、このデータが変化するとhashCode()メソッドが異なるハッシュコードを生成するので注意してください」.