Effective Java-すべてのオブジェクトの共通メソッドを再定義する場合(2):equals、hashCodeを再定義します.

8271 ワード

equalsを再定義する場合はhashCodeを再定義します


equalsを再定義するクラスはすべてhashCodeを再定義する必要があります.それ以外の場合、hashCodeの一般的な規則に違反する可能性があります.このクラスのインスタンスをHashMapやHashSetなどの集合要素として使用する場合に問題が発生します.

オブジェクト・リストの約定

  • equals比較で使用される情報が変更されていない場合は、アプリケーションの実行中に、hashCodeが何回呼び出されても同じ値を一致して返さなければなりません.
  • equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야한다.
  • equals(オブジェクト)が2つのオブジェクトが異なると判断しても、2つのオブジェクトのhashCodeが異なる値を返す必要はありません.ただし、ハッシュ・テーブルのパフォーマンスを向上させるには、他のオブジェクトに異なる値を返さなければなりません.
  • 2つ目は、hashCodeを誤って再定義した場合に問題が発生する可能性がある条項です.論理的には、同じオブジェクトは同じハッシュコードを返さなければならない.
    Map<PhoneNumber, String> m = new HashMap<>();
    m.put(new PhoneNumber(707, 867, 5309), "제니");
    
    // 아래 코드를 실행해도 "제니"는 나오지 않는다. (null 반환)
    // 논리적 동치인 이 객체는 hashCode를 정의하지 않았기 때문에 두 객체가 서로 다른 해시코드를 반환하여 두 번째 규약을 지키지 못한다.
    m.get(new PhoneNumber(707, 867, 5309));
    .
    .
    .
    
    @Override
    public int hashCode() {return 42;}
    // 이 코드처럼 hashCode를 정의하면 모든 객체가 해시테이블의 버킷 하나에 담겨 마치 연결리스트처럼 동작한다.
    // 그러면 평균 수행 시간이 O(1)인 해시테이블이 O(n)으로 느려져 쓸 수 없게 된다.
    // hashCode 함수는 서로 다른 인스턴스에 대해 다른 해시코드를 반환해야한다. 
    hash値のセット(HashSet,HashMap,HashTable)を使用すると、比較オブジェクトが論理的に同じかどうかを判断する際に、次のような処理が行われます.

    hashCodeメソッドの戻り値はまず一致し,equalsメソッドの戻り値はtrueでなければ論理的に同じオブジェクトと判断できない.
    最初の例では、hashCodeメソッドが定義されていないため、ObjectクラスのhashCodeメソッドが使用されます.
    ObjectクラスのhashCodeメソッドは、オブジェクトの一意のアドレス値をint値として返すので、各オブジェクトは異なる値を返します.
    したがって,2つのオブジェクトでequals比較を行うと,別のオブジェクトと判断する.

    HashCodeメソッドの作成


    理想的な関数は、与えられた異なるインスタンスを32ビット整数の範囲に均一に割り当てるべきである.要領を了解する.
  • int変数の結果を宣言した後、値cに初期化します.このとき、cは、ステップ2−1でオブジェクトの第1のコアフィールドを計算するハッシュコードである.(ここでのコアフィールドはequals比較用のフィールドです.)
  • は、オブジェクトの残りのコアフィールドfについて、それぞれ以下の操作を行う.
  • は、このフィールドのハッシュコードcを計算する.
  • のデフォルトタイプフィールドの場合、タイプ.hashCode(f)を実行します.ここで、Typeはこの基本タイプのparsingクラスである.(Integer.hashCode(int))
  • 参照タイプフィールドにおいて、クラスのequalsメソッドがフィールドを再帰的に呼び出すequalsによって比較される場合、このフィールドのhashCodeが再帰的に呼び出される.計算が複雑になると、フィールドの標準タイプが作成され、その標準タイプのhashCodeが呼び出されます.フィールドの値がnullの場合は0を使用します.(他の定数も可能ですが、従来は0を使用しています.)
  • フィールドが配列である場合、各コア要素は個別のフィールドとして処理される.上記のルールを再適用し、各キー要素のハッシュコードを計算し、ステップ2−2で更新する.アレイにコア要素が1つもない場合は、定数(0を推奨)のみが使用されます.すべての要素がコアであればArrayshashCodeを使用します.
  • ステップ2−1で計算されたハッシュコードcを使用して結果を更新する.コードは次のとおりです.
    ex) result = 30 * result + c;
  • は、
  • の結果を返します.
  • 上記の作業であれば、単位テストで検証できます.
    派生フィールドは、ハッシュコード計算から除外できます.つまり、他のフィールドから計算できるすべてのフィールドは無視できます.また、equals比較に使用されないフィールド「必須」は除外されます.そうでなければhashCodeの2番目の約束に違反します.
    PhoneNumberクラスに適用
    @Override
    public int hashCode() {
    	int result = Short.hashCode(areaCode);
    	result = 31 * result + Short.hashCode(prefix);
    	result = 31 * result + Short.hashCode(lineNum);
    	return result;
    }
    2−2の積31 resultは、フィールドに乗算された順序に従って結果値を変更する.その結果、クラスに複数の類似フィールドがある場合、ハッシュ効果を大幅に向上させることができる.たとえば、StringのhashCodeに乗算が実装されていない場合、すべてのアナック(構成されたスペルが同じで、順序が異なる文字列のみ)のhashCodeは同じになります.乗じた数字を31とした理由は31が奇数で少数であった.この数字が偶数でオーバーフローが発生すると、情報が失われます.2を掛けるとShift演算になるからです.小数に乗る原因は明確ではありませんが、伝統的にはそうしています.その結果、31を乗じると、この乗算の代わりにShift演算と減算を用いて最適化することができる.(31 iは(i<5)−iに等しい.)現在の仮想マシンでは、この最適化が自動的に行われます.
    PhoneNumberインスタンスの3つのコアフィールドのみを使用して単純な計算を行います.プロセス中に非決定的な要因がないので、一致するPhoneNumberインスタンスは同じハッシュコードを有するに違いない.
    Objectsクラスは、任意の数のオブジェクトを受信し、ハッシュコードを計算する静的方法hashを提供する.
    hashメソッドを使用してhashcodeの行を記述できる場合、速度は遅くなります.なぜなら、入力に基本タイプがあれば、ミシンや糸を外すこともあるからです.
    @Override
    public int hashCode() {
    	return Objects.hash(lineNum, prefix, areaCode);
    }
    クラスがfinalであり、ハッシュコードを計算するコストが高い場合は、毎回再計算するのではなく、キャッシュの方法を考慮する必要があります.このタイプのオブジェクトが主にハッシュキーとして使用される場合、インスタンスの作成時にハッシュコードを計算する必要があります.