JavaRush /Java Blog /Random-JA /equals メソッドと hashCode メソッド: 使用方法

equals メソッドと hashCode メソッド: 使用方法

Random-JA グループに公開済み
こんにちは!equals()今日は、Java の 2 つの重要なメソッド、およびについて説明しますhashCode()。私たちが彼らに会うのはこれが初めてではありません。JavaRush コースの最初に、次のような短い講義equals()ありました。忘れてしまった場合、またはこれまでに見たことがない場合は、読んでください。 メソッドは & に等しい  ハッシュコード: 使用法 - 1今日のレッスンでは、これらの概念について詳しく説明します。信じてください、話すべきことがたくさんあります。そして、何か新しいことに移る前に、これまでに説明したことを思い出してみましょう :) 覚えているように、「 」は参照を比較する==ため、「 」演算子を使用した 2 つのオブジェクトの通常の比較は悪い考えです==。最近の講義からの自動車の例を次に示します。
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car car1 = new Car();
       car1.model = "Ferrari";
       car1.maxSpeed = 300;

       Car car2 = new Car();
       car2.model = "Ferrari";
       car2.maxSpeed = 300;

       System.out.println(car1 == car2);
   }
}
コンソール出力:

false
クラス の 2 つの同一のオブジェクトを作成したように見えますCar。2 つのマシン上のすべてのフィールドは同じですが、比較の結果は依然として false です。理由はすでにわかっています。リンクcar1と がcar2メモリ内の異なるアドレスを指しているため、それらは等しくありません。ここでも、2 つの参照ではなく 2 つのオブジェクトを比較したいと考えています。オブジェクトを比較するための最良のソリューションは、 ですequals()

等しい() メソッド

このメソッドは最初から作成するのではなく、オーバーライドすることを覚えているかもしれません。結局のところ、メソッドはequals()クラスで定義されていますObject。ただし、通常の形式ではほとんど役に立ちません。
public boolean equals(Object obj) {
   return (this == obj);
}
equals()これは、クラス内で メソッドが定義される方法ですObject。同じリンクの比較。なぜ彼はこのように作られたのでしょうか?では、言語の作成者は、プログラム内のどのオブジェクトが等しいとみなされ、どのオブジェクトがそうでないとみなされるのかをどのようにして知るのでしょうか? :) これがこのメソッドの主なアイデアですequals()- クラスの作成者自身が、このクラスのオブジェクトの同等性をチェックするための特性を決定します。equals()これを行うことで、クラス内のメソッドをオーバーライドします。「特性を自分で定義する」の意味がよくわからない場合は、例を見てみましょう。これは person の単純なクラスです - Man
public class Man {

   private String noseSize;
   private String eyesColor;
   private String haircut;
   private boolean scars;
   private int dnaCode;

public Man(String noseSize, String eyesColor, String haircut, boolean scars, int dnaCode) {
   this.noseSize = noseSize;
   this.eyesColor = eyesColor;
   this.haircut = haircut;
   this.scars = scars;
   this.dnaCode = dnaCode;
}

   //getters, setters, etc.
}
2 人の人間が双子によって関連しているのか、それとも単なるドッペルゲンガーであるのかを判断する必要があるプログラムを作成しているとします。鼻の大きさ、目の色、髪型、傷跡の有無、DNA生物学的検査の結果(簡略化のため、コード番号の形で)の5つの特徴があります。これらの特徴のうち、私たちのプログラムで双子の親戚を特定できるようになると思いますか? メソッドは & に等しい  ハッシュコード: 使用法 - 2もちろん、生物学的検査のみが保証を提供できます。二人の人間が同じ目の色、髪型、鼻、さらには傷跡を持つこともあります。世界には多くの人がおり、偶然を避けることは不可能です。信頼できるメカニズムが必要です。DNA 検査の結果だけが正確な結論を導き出すことができます。これは私たちのメソッドにとって何を意味するのでしょうかequals()? Manプログラムの要件を考慮してクラス内で再定義する必要があります。このメソッドは 2 つのオブジェクトのフィールドを比較する必要がありint dnaCode、それらが等しい場合、オブジェクトは等しいことになります。
@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
本当にそんな簡単なことなのでしょうか?あまり。私たちは何かを見逃していました。この場合、オブジェクトに対して、それらの等価性が確立される「重要な」フィールドを 1 つだけ定義しましたdnaCode。ここで、そのような「重要な」フィールドが 1 つではなく 50 個あると想像してください。2 つのオブジェクトの 50 個のフィールドがすべて等しい場合、オブジェクトは等しいことになります。このようなことも起こり得ます。主な問題は、50 個のフィールドの等価性を計算するのは時間とリソースを消費するプロセスであることです。ここで、クラスに加えて、とまったく同じフィールドをMan持つクラスがあると想像してください。そして、別のプログラマがあなたのクラスを使用する場合、そのプログラマは自分のプログラムに次のようなものを簡単に書くことができます。 WomanMan
public static void main(String[] args) {

   Man man = new Man(........); //a bunch of parameters in the constructor

   Woman woman = new Woman(.........);//same bunch of parameters.

   System.out.println(man.equals(woman));
}
この場合、フィールド値をチェックする意味はありません。2 つの異なるクラスのオブジェクトを見ていることが分かりますが、原理的にそれらは等しくあり得ません。これは、メソッド内でチェックequals()、つまり 2 つの同一クラスのオブジェクトを比較する必要があることを意味します。これを考えてよかったです!
@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
しかし、もしかしたら他に何かを忘れているでしょうか?うーん...少なくとも、オブジェクトをそれ自体と比較していないことを確認する必要があります。参照 A と B がメモリ内の同じアドレスを指している場合、それらは同じオブジェクトであり、50 個のフィールドを比較する時間を無駄にする必要もありません。
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
さらに、 のチェックを追加しても問題はありませんnull。 と等しいオブジェクトは存在しませんnull。この場合、追加のチェックは意味がありません。これらすべてを考慮すると、equals()クラスメソッドはMan次のようになります。
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
上記の初期チェックをすべて実施します。次のことが判明した場合:
  • 同じクラスの 2 つのオブジェクトを比較します
  • これは同じオブジェクトではありません
  • オブジェクトを比較しているのではありませんnull
...その後、重要な特性の比較に進みます。この場合、dnaCode2 つのオブジェクトのフィールドです。メソッドをオーバーライドするときはequals()、次の要件に従ってください。
  1. 反射性。

    すべてのオブジェクトはequals()それ自体に対して存在する必要があります。
    この要件はすでに考慮されています。私たちの方法では次のように述べています。

    if (this == o) return true;

  2. 対称。

    の場合はa.equals(b) == trueb.equals(a)を返す必要がありますtrue
    私たちの方法もこの要件を満たしています。

  3. 推移性。

    2 つのオブジェクトが 3 番目のオブジェクトと等しい場合、それらは互いに等しい必要があります。および の
    場合、チェックも true を返す必要があります。a.equals(b) == truea.equals(c) == trueb.equals(c)

  4. 永続。

    作業の結果は、equals()それに含まれるフィールドが変更された場合にのみ変更される必要があります。2 つのオブジェクトのデータが変更されていない場合、チェックの結果はequals()常に同じになるはずです。

  5. との不等式null

    どのオブジェクトに対しても、チェックはa.equals(null)false を返す必要があります。
    これは単なる「有用な推奨事項」のセットではなく、Oracle のドキュメントで規定されているメソッドの厳密な規約です。

hashCode() メソッド

では、その方法について話しましょうhashCode()。なぜ必要なのでしょうか? まったく同じ目的、つまりオブジェクトを比較するためです。しかし、私たちはすでにそれを持っていますequals()!なぜ別の方法があるのでしょうか? 答えは簡単です。生産性を向上させるためです。ハッシュ関数は、Java の , メソッドで表されhashCode()、任意のオブジェクトに対して固定長の数値を返します。Java の場合、メソッドはhashCode()type の 32 ビット数値を返しますint。2 つの数値を相互に比較することは、メソッド を使用して 2 つのオブジェクトを比較するよりもはるかに高速ですequals()(特に多数のフィールドを使用する場合)。私たちのプログラムがオブジェクトを比較する場合、ハッシュ コードでこれを行う方がはるかに簡単で、それらが によって等しい場合にのみhashCode()、 による比較に進みますequals()。ちなみに、これがハッシュベースのデータ構造の仕組みです。たとえば、ご存知の構造ですHashMap。メソッド はhashCode()、 と同様にequals()、開発者自身によってオーバーライドされます。そして、 の場合と同様にequals()、このメソッドhashCode()には Oracle ドキュメントで指定されている公式の要件があります。
  1. 2 つのオブジェクトが等しい場合 (つまり、メソッドがequals()true を返す場合)、それらのオブジェクトは同じハッシュ コードを持つ必要があります。

    そうでなければ、私たちの方法は無意味になってしまいます。hashCode()前述したように、パフォーマンスを向上させるためには、によるチェックを最初に行う必要があります。ハッシュ コードが異なる場合、(メソッドで定義したようにequals()) オブジェクトが実際には等しい場合でも、チェックは false を返します。

  2. メソッドhashCode()が同じオブジェクトに対して複数回呼び出された場合、毎回同じ数値を返す必要があります。

  3. ルール 1 は逆には機能しません。2 つの異なるオブジェクトが同じハッシュ コードを持つことができます。

3 番目のルールは少しわかりにくいです。どうすればいいの?説明は非常に簡単です。メソッドはhashCode()を返しますintintは 32 ビットの数値です。値の数は、-2,147,483,648 から +2,147,483,647 までに限られています。言い換えれば、 という数値には 40 億通り以上のバリエーションがあるということですint。ここで、地球上のすべての生きている人々に関するデータを保存するプログラムを作成していると想像してください。各人は独自のクラス オブジェクトを持ちますMan。地球上には約 75 億人が住んでいます。言い換えれば、Manオブジェクトを数値に変換するためにどれほど優れたアルゴリズムを作成したとしても、十分な数値は得られません。私たちには45億の選択肢しかなく、さらに多くの人々がいます。これは、どんなに努力しても、ハッシュ コードは異なる人にとっては同じになることを意味します。この状況 (2 つの異なるオブジェクトのハッシュ コードが一致すること) は衝突と呼ばれます。メソッドをオーバーライドするときのプログラマの目標の 1 つは、hashCode()潜在的な衝突の数を可能な限り減らすことです。これらすべてのルールを考慮すると、hashCode()クラスのメソッドはどのようになりますか? Manこのような:
@Override
public int hashCode() {
   return dnaCode;
}
驚いた?:) 予想外ですが、要件を見てみると、私たちがすべてに準拠していることがわかります。私たちのオブジェクトが true を返すオブジェクトは、equals()では等しくなりますhashCode()。2 つのオブジェクトのMan値が等しいequals(つまり、同じ value を持つdnaCode) 場合、メソッドは同じ数値を返します。より複雑な例を見てみましょう。私たちのプログラムがコレクター顧客向けに高級車を選択する必要があるとします。収集は複雑な作業であり、多くの機能があります。1963 年に製造された車の価格は、1964 年に製造された同じ車の 100 倍になる場合があります。1970 年に製造された赤い車は、同じ年の同じメーカーの青い車よりも 100 倍の価格がかかることがあります。 メソッドは & に等しい  ハッシュコード: 使用法 - 4最初のケースでは、クラスを使用してMan、ほとんどのフィールド (つまり、人の特徴) を重要ではないものとして破棄し、フィールドのみを比較に使用しましたdnaCode。ここでは非常にユニークな分野に取り組んでおり、細かい点はあり得ません。これが私たちのクラスですLuxuryAuto:
public class LuxuryAuto {

   private String model;
   private int manufactureYear;
   private int dollarPrice;

   public LuxuryAuto(String model, int manufactureYear, int dollarPrice) {
       this.model = model;
       this.manufactureYear = manufactureYear;
       this.dollarPrice = dollarPrice;
   }

   //... getters, setters, etc.
}
ここで比較するときは、すべてのフィールドを考慮する必要があります。あらゆる間違いがクライアントに数十万ドルの損失をもたらす可能性があるため、安全を確保することをお勧めします。
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   if (dollarPrice != that.dollarPrice) return false;
   return model.equals(that.model);
}
私たちの方法では、equals()先ほど説明したすべてのチェックを忘れませんでした。しかしここでは、オブジェクトの 3 つのフィールドをそれぞれ比較します。このプログラムでは、あらゆる分野において平等が絶対的でなければなりません。どうですかhashCode
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
このクラスのフィールドはmodel文字列です。これは便利です。StringメソッドはhashCode()クラス内ですでにオーバーライドされています。フィールドのハッシュ コードを計算しmodel、他の 2 つの数値フィールドの合計をそれに加えます。Java には、衝突の数を減らすために使用されるちょっとしたトリックがあります。ハッシュ コードを計算するときに、中間結果に奇数の素数を掛けます。最も一般的に使用される数値は 29 または 31 です。ここでは計算の詳細には触れませんが、将来の参考のために、中間結果に十分に大きな奇数を乗算すると、ハッシュの結果を「分散」させるのに役立つことを覚えておいてください。機能し、同じハッシュコードを持つオブジェクトの数が少なくなります。LuxuryAuto のメソッドの場合、hashCode()次のようになります。
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
このメカニズムのすべての複雑さについて詳しくは、 StackOverflow のこの投稿と、Joshua Bloch の著書「Effective Java 」を 参照してください。最後に、言及すべき重要な点がもう 1 つあります。をオーバーライドするたびにequals()hashCode()オブジェクトの特定のフィールドを選択し、これらのメソッドで考慮されます。equals()しかし、との異なるフィールドを考慮できるでしょうかhashCode()? 技術的には可能です。しかし、これは悪い考えであり、その理由は次のとおりです。
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   return dollarPrice == that.dollarPrice;
}

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
LuxuryAuto クラスequals()の メソッドを次に示します。hashCode()メソッドはhashCode()変更されず、メソッドからequals()フィールドを削除しましたmodel。現在、このモデルは 2 つのオブジェクトを によって比較するための特性ではありませんequals()。ただし、ハッシュ コードを計算する際には依然として考慮されます。その結果、何が得られるでしょうか? 2台の車を作ってチェックしてみよう!
public class Main {

   public static void main(String[] args) {

       LuxuryAuto ferrariGTO = new LuxuryAuto("Ferrari 250 GTO", 1963, 70000000);
       LuxuryAuto ferrariSpider = new LuxuryAuto("Ferrari 335 S Spider Scaglietti", 1963, 70000000);

       System.out.println("Are these two objects equal to each other?");
       System.out.println(ferrariGTO.equals(ferrariSpider));

       System.out.println("What are their hash codes?");
       System.out.println(ferrariGTO.hashCode());
       System.out.println(ferrariSpider.hashCode());
   }
}

Эти два an object равны друг другу?
true
Какие у них хэш-codeы?
-1372326051
1668702472
エラー!equals()とに異なるフィールドを使用することで、hashCode()それらに対して確立された契約に違反しました。2 つの等しいequals()オブジェクトは同じハッシュ コードを持つ必要があります。私たちはそれらに対してさまざまな意味を持っています。このようなエラーは、特にハッシュを使用するコレクションを操作する場合に、最も信じられない結果につながる可能性があります。したがって、再定義する場合はequals()hashCode()同じフィールドを使用するのが正しいことになります。講義はかなり長くなってしまいましたが、今日はたくさんの新しいことを学びました!:) 問題の解決に戻りましょう!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION