Javaオブジェクトをequals()およびhashcode()と比較する

このJavaチャレンジャーでは、Javaプログラムでオブジェクトの比較を効率的かつ簡単にする方法equals()hashcode()組み合わせを学習します。簡単に言えば、これらのメソッドは連携して、2つのオブジェクトが同じ値を持っているかどうかを確認します。  

これがないequals()と、オブジェクトのすべてのフィールドを比較して、hashcode()非常に大きな「if」比較を作成する必要があります。これにより、コードが非常に混乱し、読みにくくなります。これら2つの方法を組み合わせることで、より柔軟でまとまりのあるコードを作成できます。

Javaチャレンジャーのソースコードを入手してください。

Javaでequals()とhashcode()をオーバーライドする

メソッドのオーバーライドは、ポリモーフィズムを利用するために、親クラスまたはインターフェイスの動作がサブクラスに再度書き込まれる(オーバーライドされる)手法です。ObjectJavaのすべてにequals()hashcode()メソッドが含まれていますが、正しく機能するにはそれらをオーバーライドする必要があります。

オーバーライドがequals()と  hashcode()でどのように機能するかを理解するために、コアJavaクラスでのそれらの実装を調べることができます。以下は、クラスのequals()メソッドObjectです。このメソッドは、現在のインスタンスが以前に渡されたものと同じであるかどうかをチェックしていObjectます。

 public boolean equals(Object obj) { return (this == obj); } 

hashcode()メソッドがオーバーライドされていない場合、Objectクラスのデフォルトメソッドが呼び出されます。これはネイティブメソッドです。つまり、Cなどの別の言語で実行され、オブジェクトのメモリアドレスに関するコードが返されます。(JDKコードを記述していない限り、このメソッドがどのように機能するかを正確に知ることはそれほど重要ではありません。)

 @HotSpotIntrinsicCandidate public native int hashCode(); 

equals()andhashcode()メソッドがオーバーライドされていない場合、代わりに上記のメソッドが呼び出されます。この場合、メソッドは、2つ以上のオブジェクトが同じ値を持っているかどうかをチェックするというequals()andの本当の目的を果たしていませんhashcode()

原則として、オーバーライドequals()するときは、もオーバーライドする必要がありますhashcode()

オブジェクトとequals()の比較

このequals()メソッドを使用して、Javaでオブジェクトを比較します。2つのオブジェクトが同じであるかどうかを判断するためにequals()、オブジェクトの属性の値を比較します。

 public class EqualsAndHashCodeExample { public static void main(String... equalsExplanation) { System.out.println(new Simpson("Homer", 35, 120) .equals(new Simpson("Homer",35,120))); System.out.println(new Simpson("Bart", 10, 120) .equals(new Simpson("El Barto", 10, 45))); System.out.println(new Simpson("Lisa", 54, 60) .equals(new Object())); } static class Simpson { private String name; private int age; private int weight; public Simpson(String name, int age, int weight) { this.name = name; this.age = age; this.weight = weight; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Simpson simpson = (Simpson) o; return age == simpson.age && weight == simpson.weight && name.equals(simpson.name); } } } 

最初の比較でequals()は、現在のオブジェクトインスタンスを渡されたオブジェクトと比較します。2つのオブジェクトの値が同じである場合、equals()はを返しtrueます。

2番目の比較でequals()は、渡されたオブジェクトがnullであるかどうか、または別のクラスとして入力されているかどうかを確認します。それが別のクラスである場合、オブジェクトは等しくありません。

最後にequals()、オブジェクトのフィールドを比較します。2つのオブジェクトのフィールド値が同じである場合、オブジェクトは同じです。

オブジェクト比較の分析

それでは、これらの比較の結果を私たちのmain()方法で見てみましょう。まず、2つのSimpsonオブジェクトを比較します。

 System.out.println(new Simpson("Homer", 35, 120).equals(new Simpson("Homer", 35, 120))); 

ここのオブジェクトは同一であるため、結果はになりますtrue

次に、2つのSimpsonオブジェクトを再度比較します。

 System.out.println(new Simpson("Bart", 10, 45).equals(new Simpson("El Barto", 10, 45))); 

ここのオブジェクトはほぼ同じですが、名前が異なります:バートとエルバルト。したがって、結果はになりますfalse

最後に、SimpsonオブジェクトとクラスObjectのインスタンスを比較してみましょう。

 System.out.println(new Simpson("Lisa", 54, 60).equals(new Object())); 

この場合、結果はfalseクラスタイプが異なるためになります。

equals()対==

一見、==演算子とequals()メソッドは同じことをしているように見えるかもしれませんが、実際にはそれらは異なって機能します。==オペレータは、2つのオブジェクト参照が同じオブジェクトを指しているかどうか比較します。例えば:

 System.out.println(homer == homer2); 

最初の比較ではSimpsonnew演算子を使用して2つの異なるインスタンスをインスタンス化しました。このため、変数homerhomer2Objectメモリヒープ内のさまざまな参照を指します。false結果として得られます。

System.out.println(homer.equals(homer2)); 

2番目の比較では、equals()メソッドをオーバーライドします。この場合、名前のみが比較されます。両方のSimpsonオブジェクトの名前が「Homer」であるため、結果はになりますtrue

hashcode()でオブジェクトを一意に識別する

このhashcode()メソッドを使用して、オブジェクトを比較するときにパフォーマンスを最適化します。実行  hashcode()すると、プログラム内のオブジェクトごとに一意のIDが返されます。これにより、オブジェクトの状態全体を比較するタスクがはるかに簡単になります。

オブジェクトのハッシュコードが別のオブジェクトのハッシュコードと同じでない場合、equals()メソッドを実行する理由はありません。2つのオブジェクトが同じではないことがわかっているだけです。一方、ハッシュコード同じである場合は、equals()メソッドを実行して、値とフィールドが同じであるかどうかを判断する必要があります。

を使用した実際の例を次に示しhashcode()ます。

 public class HashcodeConcept { public static void main(String... hashcodeExample) { Simpson homer = new Simpson(1, "Homer"); Simpson bart = new Simpson(2, "Homer"); boolean isHashcodeEquals = homer.hashCode() == bart.hashCode(); if (isHashcodeEquals) { System.out.println("Should compare with equals method too."); } else { System.out.println("Should not compare with equals method because " + "the id is different, that means the objects are not equals for sure."); } } static class Simpson { int id; String name; public Simpson(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o)  if (this == o) return true; if (o == null  @Override public int hashCode() { return id; } } } 

hashcode()常に同じ値を返すAは有効ですが、あまり効果的ではありません。この場合、比較は常にを返すtrueため、equals()メソッドは常に実行されます。この場合、パフォーマンスの向上はありません。  

コレクションでequals()とhashcode()を使用する

The Set interface is responsible for ensuring no duplicate elements will be inserted in a Set subclass. The following are some of the classes that implement the Set interface:

  • HashSet
  • TreeSet
  • LinkedHashSet
  • CopyOnWriteArraySet

Only unique elements may be inserted into a Set, so if you want to add an element to the HashSet class (for example), you must first use the equals() and hashcode() methods to verify that the element is unique. If the equals() and hashcode()methods weren’t overridden in this case, you would risk inserting duplicate elements in the code.

In the code below, we’re using the add method to add a new element  to a HashSet object. Before the new element is added, HashSet checks to see whether the element  already exists in the given collection:

 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; 

If the object is the same, the new element won’t be inserted.

Hash collections

Set isn’t the only collection that makes use of equals() and hashcode(). HashMap, Hashtable, and LinkedHashMap also require these methods. As a rule, if you see a collection that has the prefix of “Hash,” you can be sure that it requires overriding the hashcode() and equals() methods to make their features work properly.  

Guidelines for using equals() and hashcode()

You should only execute an equals() method for objects that have the same unique hashcode ID. You should not execute equals() when the hashcode ID is different.

Table 1. Hashcode comparisons

If the hashcode() comparison ... Then …
returns true execute equals()
returns false do not execute equals()

This principle is mainly used in Set or Hash collections for performance reasons.

Rules for object comparison

When a hashcode() comparison returns false, the equals() method must also return false. If the hashcode is different, then the objects are definitely not equal.

Table 2. Object comparison with hashcode()

When the hashcode comparison returns ... The equals() method should return ...
true true or false
false false

When the equals() method returns true, it means that the objects are equal in all values and attributes. In this case,  the hashcode comparison must be true as well.

Table 3. Object comparison with equals()

When the equals() method returns ... The hashcode() method should return ...
true true
false true or false

Take the equals() and hashcode() challenge!

It’s time to test your skills with the equals() and hashcode() methods.  Your goal in this challenge is to figure out the output of the two equals() method comparisons and guess the size of the Set collection.

To start, study the following code carefully:

 public class EqualsHashCodeChallenge { public static void main(String... doYourBest) { System.out.println(new Simpson("Bart").equals(new Simpson("Bart"))); Simpson overriddenHomer = new Simpson("Homer") { public int hashCode() { return (43 + 777) + 1; } }; System.out.println(new Simpson("Homer").equals(overriddenHomer)); Set set = new HashSet(Set.of(new Simpson("Homer"), new Simpson("Marge"))); set.add(new Simpson("Homer")); set.add(overriddenHomer); System.out.println(set.size()); } static class Simpson { String name; Simpson(String name) { this.name = name; } @Override public boolean equals(Object obj) { Simpson otherSimpson = (Simpson) obj; return this.name.equals(otherSimpson.name) && this.hashCode() == otherSimpson.hashCode(); } @Override public int hashCode() { return (43 + 777); } } } 

Remember, analyze the code first, guess the result, and then run the code. Your goal is to improve your skill with code analysis and absorb core Java concepts to make your code more powerful. Choose your answer before checking the correct answer below.

 A) true true 4 B) true false 3 C) true false 2 D) false true 3 

What just happened? Understanding equals() and hashcode()

In the first equals() method comparison, the result is true because the state of the object is exactly the same and the hashcode() method returns the same value for both objects.

In the second equals() method comparison, the hashcode() method is being overridden for the overridenHomer variable. The name is “Homer” for both Simpson objects, but the hashcode() method returns a different value for overriddenHomer. In this case, the final result from the the equals() method will be false because the method contains a comparison with the hashcode.

You might notice that the size of the collection is set to hold three Simpson objects. Let’s check this in a detailed way.

The first object in the set will be will be inserted normally:

 new Simpson("Homer"); 

The next object will be inserted normally, as well, because it holds a different value from the previous object:

 new Simpson("Marge"); 

Finally,  the following Simpson object has the same value as the first object. In this case the object won’t be inserted:

 set.add(new Simpson("Homer")); 

ご存知のとおり、overridenHomerオブジェクトは通常のSimpson(“Homer”)インスタンス化とは異なるハッシュコード値を使用します。このため、この要素はコレクションに挿入されます。

 overriddenHomer; 

解答

このJavaチャレンジャーへの答えはBです。出力は次のようになります。

 true false 3 

ビデオチャレンジ!equals()とhashcode()のデバッグ

デバッグは、コードを改善しながらプログラミングの概念を完全に吸収する最も簡単な方法の1つです。このビデオでは、Javaequals()hashcode()チャレンジをデバッグして説明している間、フォローすることができます。