最近私はこれを読みました Developer Works Document 。
この文書はhashCode()
とequals()
を効果的かつ正確に定義することに関するものですが、なぜこれら2つのメソッドをオーバーライドする必要があるのか私は理解できません。
これらのメソッドを効率的に実装するためにどのようにして決定を下すことができますか?
Joshua Blochが効果的なJavaについて語る
Equals()をオーバーライドするすべてのクラスでhashCode()をオーバーライドする必要があります。そうしないと、Object.hashCode()の一般規約に違反することになり、クラスがHashMap、HashSet、およびHashtableを含むすべてのハッシュベースのコレクションと正しく連携して機能しなくなります。
equals()
をオーバーライドせずにhashCode()
をオーバーライドしてMap
を使用しようとするとどうなるかを例にとり、理解してみましょう。
このようなクラスがあり、MyClass
の2つのオブジェクトがimportantField
が等しい場合は等しい(Eclipseによって生成されたhashCode()
とequals()
がある)とします。
public class MyClass {
private final String importantField;
private final String anotherField;
public MyClass(final String equalField, final String anotherField) {
this.importantField = equalField;
this.anotherField = anotherField;
}
public String getEqualField() {
return importantField;
}
public String getAnotherField() {
return anotherField;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((importantField == null) ? 0 : importantField.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MyClass other = (MyClass) obj;
if (importantField == null) {
if (other.importantField != null)
return false;
} else if (!importantField.equals(other.importantField))
return false;
return true;
}
}
equals
のみオーバーライド
equals
のみがオーバーライドされている場合、最初にmyMap.put(first,someValue)
を呼び出すと何らかのバケットにハッシュされ、myMap.put(second,someOtherValue)
を呼び出すと別のバケットにハッシュされます(異なるhashCode
があるため)。したがって、それらは同じですが、同じバケットにハッシュされないため、マップはそれを認識できず、両方ともマップ内に残ります。
equals()
をオーバーライドする場合はhashCode()
をオーバーライドする必要はありませんが、MyClass
の2つのオブジェクトが等しい場合にimportantField
の2つのオブジェクトが等しいことがわかっている場合、equals()
をオーバーライドしないでください。
hashCode
のみオーバーライド
あなたがこれを持っていると想像してください
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
hashCode
をオーバーライドするだけの場合は、myMap.put(first,someValue)
を呼び出すときに最初にかかり、そのhashCode
を計算して指定のバケットに格納します。それからmyMap.put(second,someOtherValue)
を呼び出すと、 Map Documentation のようにfirstがsecondに置き換えられます(ビジネス要件に従って)。
しかし、問題はequalsが再定義されていないため、マップがsecond
をハッシュし、second.equals(k)
がtrueになるようにオブジェクトk
を探してバケットを反復処理すると、second.equals(first)
はfalse
にはなりません。
それが明らかだったことを願っています
HashMap
やHashSet
などのコレクションは、オブジェクトの hashcode 値を使用してコレクション内に格納する方法を決定し、 hashcode を使用してコレクション内のオブジェクトを見つけます。
ハッシュ検索は2段階のプロセスです。
hashCode()
を使う)equals()
を使用)これがequals()
とhashcode()
をオーバーライドする理由の小さな例です。
Ageとnameの2つのフィールドを持つEmployee
クラスを考えてみましょう。
public class Employee {
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Employee))
return false;
Employee employee = (Employee) obj;
return employee.getAge() == this.getAge()
&& employee.getName() == this.getName();
}
// commented
/* @Override
public int hashCode() {
int result=17;
result=31*result+age;
result=31*result+(name!=null ? name.hashCode():0);
return result;
}
*/
}
次にクラスを作成し、Employee
オブジェクトをHashSet
に挿入し、そのオブジェクトが存在するかどうかをテストします。
public class ClientTest {
public static void main(String[] args) {
Employee employee = new Employee("rajeev", 24);
Employee employee1 = new Employee("rajeev", 25);
Employee employee2 = new Employee("rajeev", 24);
HashSet<Employee> employees = new HashSet<Employee>();
employees.add(employee);
System.out.println(employees.contains(employee2));
System.out.println("employee.hashCode(): " + employee.hashCode()
+ " employee2.hashCode():" + employee2.hashCode());
}
}
次のように表示されます。
false
employee.hashCode(): 321755204 employee2.hashCode():375890482
hashcode()
メソッドのコメントを外し、同じコマンドを実行すると、出力は次のようになります。
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
2つのオブジェクトが等しいと見なされた場合、それらの hashcode sも等しくなければならないのはなぜでしょうか。そうでなければ、Objectクラスのデフォルトの hashcode メソッドは、equals()
メソッドが2つのようにオーバーライドされていても、各オブジェクトに対してほぼ常に一意の番号を持つため、オブジェクトを見つけることができません。より多くのオブジェクトが等しいと見なされます。 hashcode sがそれを反映していなければ、オブジェクトがどれほど等しいかは関係ありません。だからもう一度もう一度:2つのオブジェクトが等しいならば、それらの hashcode sも同様に等しくなければなりません。
Equals()をオーバーライドするすべてのクラスでhashCode()をオーバーライドする必要があります。そうしないと、Object.hashCode()の一般規約に違反することになり、クラスがHashMap、HashSet、およびHashtableを含むすべてのハッシュベースのコレクションと適切に機能しなくなります。
from 有効なJava 、Joshua Bloch著
equals()
とhashCode()
を一貫して定義することで、ハッシュベースのコレクションのキーとしてクラスの使いやすさを向上させることができます。 hashCodeのAPIドキュメントでは、次のように説明されています。「このメソッドは、Java.util.Hashtable
によって提供されるようなハッシュテーブルのためにサポートされています。」
これらのメソッドを効率的に実装する方法についてのあなたの質問に対する最良の答えは、 Effective Java の第3章を読むことをお勧めすることです。
簡単に言うと、Objectのequalsメソッドは参照の等価性をチェックします。ここで、プロパティが等しい場合、クラスの2つのインスタンスは意味的に等しい可能性があります。これは例えば、 HashMap や Set のようにequalsとハッシュコードを利用するコンテナにあなたのオブジェクトを入れるときに重要です。次のようなクラスがあるとしましょう。
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
}
同じ id を持つ2つのインスタンスを作成します。
Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");
Equalsを上書きせずに、次のようになります。
正しい?たぶん、これがあなたが望むものならば。しかし、2つの異なるインスタンスであるかどうかにかかわらず、同じIDを持つオブジェクトを同じオブジェクトにしたいとしましょう。 equals(およびhashcode)をオーバーライドします。
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
@Override
public boolean equals(Object other) {
if (other instanceof Foo) {
return ((Foo)other).id.equals(this.id);
}
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}
Equalsとhashcodeの実装に関しては Guavaのヘルパーメソッドを使うことをお勧めします
アイデンティティは平等ではありません。
==
テストIDと等しい。equals(Object obj)
メソッドは等価性テストを比較します(つまり、メソッドをオーバーライドして等価性を伝える必要があります)。なぜJavaでequalsおよびhashCodeメソッドをオーバーライドする必要があるのですか?
まず、equalsメソッドの使い方を理解する必要があります。
2つのオブジェクト間の違いを識別するために、equalsメソッドをオーバーライドする必要があります。
例えば:
Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.
------------------------------
Now I have overriden Customer class equals method as follows:
@Override
public boolean equals(Object obj) {
if (this == obj) // it checks references
return true;
if (obj == null) // checks null
return false;
if (getClass() != obj.getClass()) // both object are instances of same class or not
return false;
Customer other = (Customer) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference
return false;
return true;
}
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2); // returns true by our own logic
hashCodeメソッドが簡単に理解できるようになりました。
hashCodeは、 HashMap 、 HashSet のようなデータ構造にオブジェクトを格納するために整数を生成します。
上記のようにCustomer
のoverride equalsメソッドがあるとします。
customer1.equals(customer2); // returns true by our own logic
オブジェクトをバケットに格納するときにデータ構造を操作しながら(バケットはフォルダの派手な名前です)。組み込みハッシュ技術を使用する場合、上記の2人の顧客に対しては2つの異なるハッシュコードが生成されます。したがって、同じオブジェクトを2つの異なる場所に格納しています。このような問題を避けるために、次の原則に基づいてhashCodeメソッドをオーバーライドする必要があります。
わかりました、概念を非常に簡単な言葉で説明しましょう。
まず、より広い観点から、私たちはコレクションを持っています、そしてハッシュマップはコレクションの中のデータ構造の1つです。
最初にハッシュマップとは何か、また何がするのかを理解する必要がある場合は、equalsとhashcodeの両方のメソッドをオーバーライドする必要がある理由を理解するため。
ハッシュマップは、データのキーと値のペアを配列形式で格納するデータ構造です。 a []としましょう。 'a'の各要素はキーと値のペアです。
また、上記の配列の各インデックスはリンクリストにすることができ、それによって1つのインデックスに複数の値を持つことができます。
では、なぜハッシュマップが使われるのでしょうか。大規模な配列を検索しなければならない場合は、効率的に検索できない場合はそれぞれを検索する必要があります。ハッシュ技法を使用すると、配列を何らかのロジックで前処理し、そのロジックに基づいて要素をグループ化できます。
例:配列1,2,3,4,5,6,7,8,9,10,11があり、ハッシュ関数mod 10を適用するので、1,11が一緒にグループ化されます。したがって、前の配列で11を検索する必要がある場合は、配列全体を反復する必要がありますが、グループ化すると反復の範囲が制限され、それによって速度が向上します。上記のすべての情報を格納するために使用されるそのデータ構造は、単純にするために2次元配列と考えることができます。
上記のハッシュマップとは別に、重複マップを追加しないように指示します。これが、equalsとhashcodeをオーバーライドしなければならない主な理由です。
ハッシュマップの内部的な働きを説明していると言われたら、ハッシュマップがどんなメソッドを持っていて、それが私が上で説明した上記の規則にどのように従っているかを見つける必要があります。
そのため、ハッシュマップはput(K、V)と呼ばれるメソッドを持ち、ハッシュマップに従って、配列を効率的に分散し、重複を追加しないという上記の規則に従う必要があります。
つまり、指定されたキーのハッシュコードを最初に生成して、どのインデックスに値を入れるべきかを決定するということです。そのインデックスに何も存在しない場合は、新しい値が追加されます。それから新しい値はそのインデックスでリンクリストの終わりの後に加えられるべきです。ただし、ハッシュマップの目的の動作に従って重複を追加しないでください。したがって、2つのIntegerオブジェクトaa = 11、bb = 11があるとしましょう。オブジェクトクラスから派生したすべてのオブジェクトと同様に、2つのオブジェクトを比較するためのデフォルトの実装では、オブジェクト内の値ではなく参照を比較します。そのため、上記の場合、意味的に等しいとはいえ同等性テストに失敗し、ハッシュコードと値が同じ2つのオブジェクトが存在する可能性があり、それによって重複が発生します。オーバーライドすれば、重複の追加を避けることができます。 詳細作業 を参照することもできます。
import Java.util.HashMap;
public class Employee {
String name;
String mobile;
public Employee(String name,String mobile) {
this.name=name;
this.mobile=mobile;
}
@Override
public int hashCode() {
System.out.println("calling hascode method of Employee");
String str=this.name;
Integer sum=0;
for(int i=0;i<str.length();i++){
sum=sum+str.charAt(i);
}
return sum;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
System.out.println("calling equals method of Employee");
Employee emp=(Employee)obj;
if(this.mobile.equalsIgnoreCase(emp.mobile)){
System.out.println("returning true");
return true;
}else{
System.out.println("returning false");
return false;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Employee emp=new Employee("abc", "hhh");
Employee emp2=new Employee("abc", "hhh");
HashMap<Employee, Employee> h=new HashMap<>();
//for (int i=0;i<5;i++){
h.put(emp, emp);
h.put(emp2, emp2);
//}
System.out.println("----------------");
System.out.println("size of hashmap: "+h.size());
}
}
hashCode()
:
ハッシュコード方式をオーバーライドするだけでは何も起こりません。なぜなら、それは常に各オブジェクトの新しいhashCode
をObjectクラスとして返すからです。
equals()
:
Equalメソッドのみをオーバーライドする場合、a.equals(b)
はtrueです。これは、aとbのhashCode
が同じでなければならないことを意味しますが、起こりません。 hashCode
メソッドをオーバーライドしていないからです。
注:ObjectクラスのhashCode()
メソッドは常に各オブジェクトに対して新しいhashCode
を返します。
したがって、ハッシュベースのコレクションでオブジェクトを使用する必要がある場合は、equals()
とhashCode()
の両方をオーバーライドする必要があります。
HashMap、Hashtableなどのようなコレクション内のキーとして独自のクラスオブジェクトを使用するには、コレクションの内部動作を認識することで両方のメソッド(hashCode()とequals())をオーバーライドする必要があります。さもなければ、それは私達が期待していない間違った結果をもたらします。
@Lomboの回答に追加する
いつequals()をオーバーライドする必要がありますか?
Objectのequals()のデフォルト実装は次のとおりです。
public boolean equals(Object obj) {
return (this == obj);
}
つまり、2つのオブジェクトが同じメモリアドレスを持っている場合にのみ等しいと見なされます。これは、オブジェクトをそれ自体と比較している場合にのみ当てはまります。
しかし、2つのオブジェクトが1つ以上のプロパティに対して同じ値を持つ場合、それらを同じと見なすことをお勧めします(@Lomboの回答で示されている例を参照)。
それで、あなたはこれらの状況でequals()
をオーバーライドするでしょう、そしてあなたは平等のためにあなた自身の条件を与えるでしょう。
私はequals()の実装に成功し、それはうまく機能しています。なぜ彼らはhashCode()もオーバーライドするよう求めているのでしょうか。
ユーザー定義クラスで "Hash"ベースのCollections を使用しない限り、問題ありません。しかし、将来的にはHashMap
やHashSet
を使いたくなるかもしれません。もしoverride
や hashCode() を正しく実装していなければ、これらのHashベースのコレクションは意図したように動作しません。
等しいのみをオーバーライドする(@Lomboの回答への追加)
myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?
まず最初に、HashMapはsecond
のhashCodeがfirst
と同じかどうかをチェックします。値が同じ場合のみ、同じバケット内の同等性のチェックに進みます。
しかし、ここでhashCodeはこれら2つのオブジェクトでは異なります(それらはデフォルトの実装とは異なるメモリアドレスを持っているからです)。それゆえ、平等をチェックすることすら気にしないでしょう。
オーバーライドされたequals()メソッド内にブレークポイントがある場合、それらが異なるhashCodeを持っていてもステップインしません。 contains()
はhashCode()
をチェックし、それらが同じである場合に限りequals()
メソッドを呼び出します。
HashMapですべてのバケットの等価性をチェックできないのはなぜですか。だから私はhashCode()をオーバーライドする必要はありません!!
それからあなたはハッシュベースのコレクションのポイントを逃しています。次の点を考慮してください。
Your hashCode() implementation : intObject%9.
以下は、バケットの形式で格納されているキーです。
Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...
たとえば、マップにキー10が含まれているかどうかを知りたいとします。すべてのバケットを検索しますか?または1つのバケットだけを検索しますか?
HashCodeに基づいて、10が存在する場合、それがBucket 1に存在している必要があることを特定できます。したがって、Bucket 1のみが検索されます。
オーバーライドしないと、Objectのデフォルトの実装が使用されるためです。
インスタンスの等価性とハッシュコードの値は、オブジェクトを構成する要素に関する知識を必要とするため、具体的な意味を持たせるには、クラスで定義し直す必要があります。
class A {
int i;
// Hashing Algorithm
if even number return 0 else return 1
// Equals Algorithm,
if i = this.i return true else false
}
hashCode()
を使用してバケットを決定し、equals()
メソッドを使用して値がすでにBucketに存在するかどうかを調べます。そうでなければそれは追加されますそれ以外の場合は現在の値に置き換えられますhashCode()
を使い、Entry内の値を見つけるためにequals()
を使います。両方が上書きされた場合、
地図< _ a _ >
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
equalsがオーバーライドされていない場合
地図< _ a _ >
Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..
HashCodeがオーバーライドされていない場合
地図< _ a _ >
Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...
ハッシュコード均等契約
Javaは次のような規則を置きます。
「Objectクラスのequalsメソッドを使用して2つのオブジェクトが等しい場合、ハッシュコードメソッドはこれら2つのオブジェクトに対して同じ値を設定する必要があります。」
そのため、クラス内でequals()
をオーバーライドする場合は、この規則に従うためにもhashcode()
メソッドをオーバーライドする必要があります。両方のメソッド(equals()
とhashcode()
)は、例えば、値をキーと値のペアとして格納するためにHashtable
で使用されます。一方を上書きし、もう一方を上書きしない場合、そのようなオブジェクトをキーとして使用すると、Hashtable
が意図したとおりに機能しない可能性があります。
バケツの中にボールをすべて黒色で集めることを検討してください。あなたの仕事はこれらのボールを次のように色付けして適切なゲームに使うことです。
テニス用 - 黄色、赤。クリケット用 - ホワイト
今バケツは黄色、赤と白の3色のボールを持っています。そして今それは あなたは着色をしましたあなただけがどの色がどのゲームのためのものかを知っています。
ボールを着色 - ハッシング。ゲームのためのボールの選択 - 等しい。
あなたが着色をし、誰かがクリケットかテニスのどちらかのためにボールを選ぶならば、彼らは色を気にしません!
1) 以下の例によくある間違いを示します。
public class Car {
private String color;
public Car(String color) {
this.color = color;
}
public boolean equals(Object obj) {
if(obj==null) return false;
if (!(obj instanceof Car))
return false;
if (obj == this)
return true;
return this.color.equals(((Car) obj).color);
}
public static void main(String[] args) {
Car a1 = new Car("green");
Car a2 = new Car("red");
//hashMap stores Car type and its quantity
HashMap<Car, Integer> m = new HashMap<Car, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Car("green")));
}
}
緑色の車が見つかりません
2. hashCode()による問題
この問題は、オーバーライドされていないメソッドhashCode()
が原因で発生します。 equals()
とhashCode()
の間の契約は:
2つのオブジェクトが同じハッシュコードを持っている場合、それらは等しくても等しくなくても構いません。
public int hashCode(){
return this.color.hashCode();
}
私は説明を検討していました "hashCodeをオーバーライドする場合、myMap.put(first,someValue)
を呼び出すときに最初に取得し、そのhashCodeを計算して特定のバケットに格納します。次にmyMap.put(first,someOtherValue)
を呼び出すときに2番目は、マップドキュメントによると、同じであるためです(定義によると)。」 :
myMap
を追加するのは2回目で、myMap.put(second,someOtherValue)
のような「2番目」のオブジェクトでなければなりません
JavaのequalsメソッドとHashcodeメソッド
それらはすべてのクラスのスーパークラスであるJava.lang.Objectクラスのメソッドです(カスタムクラスもJava APIで定義された他のクラスも)。
実装:
public boolean equals(Object obj)
public int hashCode()
public boolean equals(Object obj)
このメソッドは、2つのオブジェクト参照xとyが同じオブジェクトを参照しているかどうかをチェックするだけです。すなわち、x == yかどうかをチェックします。
それは再帰的です:どんな参照値xに対しても、x.equals(x)はtrueを返すべきです。
これは対称です:任意の参照値xとyについて、x.equals(y)は、y.equals(x)がtrueを返す場合に限りtrueを返します。
これは推移的である:任意の参照値x、y、およびzに対して、x.equals(y)がtrueを返し、y.equals(z)がtrueを返す場合、x.equals(z)はtrueを返すはずです。
一貫性がある:任意の参照値xおよびyについて、オブジェクトの等価比較で使用される情報が変更されていない限り、x.equals(y)を複数回呼び出しても常にtrueまたはfalseが返されます。
Null以外の参照値xの場合、x.equals(null)はfalseを返す必要があります。
public int hashCode()
このメソッドは、このメソッドが呼び出されたオブジェクトのハッシュコード値を返します。このメソッドはハッシュコード値を整数として返し、Hashtable、HashMap、HashSetなどのようなハッシュベースのコレクションクラスのためにサポートされています。このメソッドは、equalsメソッドをオーバーライドするすべてのクラスでオーバーライドする必要があります。
hashCodeの一般規約は次のとおりです。
Javaアプリケーションの実行中に同じオブジェクトに対して複数回呼び出される場合は、オブジェクトの等価比較で使用される情報が変更されていない限り、hashCodeメソッドは常に同じ整数を返す必要があります。
この整数は、アプリケーションの実行ごとに同じアプリケーションの実行ごとに一貫性を保つ必要はありません。
2つのオブジェクトがequals(Object)メソッドに従って等しい場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、同じ整数結果が得られます。
2つのオブジェクトがequals(Java.lang.Object)メソッドに従って等しくない場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、異なる整数結果が得られる必要はありません。ただし、プログラマは、等しくないオブジェクトに対して異なる整数結果を生成すると、ハッシュテーブルのパフォーマンスが向上する可能性があることに注意する必要があります。
等しいオブジェクトは等しい限り同じハッシュコードを生成しなければなりませんが、異なるオブジェクトは異なるハッシュコードを生成する必要はありません。
リソース:
メソッドequalsとhashcodeはオブジェクトクラスで定義されています。 equalsメソッドがtrueを返す場合、デフォルトでシステムはさらに進み、ハッシュコードの値をチェックします。 2つのオブジェクトのハッシュコードも同じである場合のみ、オブジェクトは同じと見なされます。したがって、equalsメソッドのみをオーバーライドすると、オーバーライドされたequalsメソッドが2つのオブジェクトが等しいことを示していても、システム定義のハッシュコードは2つのオブジェクトが等しいことを示さない場合があります。そのため、ハッシュコードも上書きする必要があります。
他の2つの(B)(C)を集約するクラス(A)があり、ハッシュテーブル内に(A)のインスタンスを格納する必要があるとします。デフォルトの実装ではインスタンスの識別のみが可能ですが、(B)と(C)による識別はできません。したがって、Aの2つのインスタンスは等しくなる可能性がありますが、デフォルトではそれらを正しい方法で比較することはできません。
Value Objects を使用する場合に便利です。以下は、 Portland Pattern Repository からの抜粋です。
値オブジェクトの例としては、数値、日付、金額、文字列などがあります。通常、それらは非常に広く使用されている小さなオブジェクトです。彼らのアイデンティティは、彼らのオブジェクトアイデンティティよりもむしろ彼らの状態に基づいています。このようにして、同じ概念値オブジェクトの複数のコピーを持つことができます。
そのため、1998年1月16日の日付を表すオブジェクトのコピーを複数持つことができます。これらのコピーはどれも同じものになります。このような小さなオブジェクトの場合、日付を表すのに単一のオブジェクトに頼るよりも、新しいオブジェクトを作成して移動する方が簡単なことがよくあります。
値オブジェクトは、Javaでは常に.equals()(またはSmalltalkでは=)をオーバーライドする必要があります。 (.hashCode()もオーバーライドすることを忘れないでください。)
文字列クラスとラッパークラスは、Objectクラスとは異なるequals()
およびhashCode()
メソッドの実装を持っています。 Objectクラスのequals()メソッドは、内容ではなくオブジェクトの参照を比較します。 ObjectクラスのhashCode()メソッドは、内容が同じであるかどうかにかかわらず、すべての単一オブジェクトについて異なるハッシュコードを返します。
Mapコレクションを使用していて、キーがPersistent型、StringBuffer/builder型の場合、問題が発生します。 Stringクラスと異なり、equals()とhashCode()をオーバーライドしないため、equals()は、2つの異なるオブジェクトを比較したときに同じ内容であってもfalseを返します。 hashMapに同じコンテンツキーを格納させます。同じコンテンツキーを保存すると、Mapでは重複キーが許可されないため、Mapのルールに違反します。したがって、クラス内でequals()メソッドとhashCode()メソッドをオーバーライドして、それらがStringのequals()とhashCode()と同じように機能し、同じコンテンツキーが使用されないように実装を提供します。
Equals()はハッシュコードに従って機能するため、equals()と共にhashCode()メソッドをオーバーライドする必要があります。
さらに、equals()と一緒にhashCode()メソッドをオーバーライドすることは、equals() - hashCode()規約を損なわないために役立ちます。「2つのオブジェクトが等しい場合、それらは同じハッシュコードを持たなければなりません。」
いつhashCode()のためのカスタム実装を書く必要がありますか?
私たちが知っているように、HashMapの内部作業はHashingの原則に基づいています。エントリセットが格納される特定のバケットがあります。同じカテゴリオブジェクトを同じインデックスに格納できるように、要件に応じてhashCode()実装をカスタマイズします。 put(k,v)
methodを使用して値をMapコレクションに格納すると、put()の内部実装は次のようになります。
put(k, v){
hash(k);
index=hash & (n-1);
}
つまり、インデックスを生成し、インデックスは特定のキーオブジェクトのハッシュコードに基づいて生成されます。同じハッシュコードエントリセットが同じバケットまたはインデックスに格納されるため、この方法では要件に応じてハッシュコードを生成します。
それでおしまい!
以下の例で、Personクラスのequalsまたはhashcodeのオーバーライドをコメントアウトすると、このコードはTomの順序を検索できません。ハッシュコードのデフォルト実装を使用すると、ハッシュテーブル検索で失敗する可能性があります。
私が以下に持っているものは人によって人々の注文を引き上げる単純化されたコードです。人はハッシュテーブルのキーとして使われています。
public class Person {
String name;
int age;
String socialSecurityNumber;
public Person(String name, int age, String socialSecurityNumber) {
this.name = name;
this.age = age;
this.socialSecurityNumber = socialSecurityNumber;
}
@Override
public boolean equals(Object p) {
//Person is same if social security number is same
if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
return true;
} else {
return false;
}
}
@Override
public int hashCode() { //I am using a hashing function in String.Java instead of writing my own.
return socialSecurityNumber.hashCode();
}
}
public class Order {
String[] items;
public void insertOrder(String[] items)
{
this.items=items;
}
}
import Java.util.Hashtable;
public class Main {
public static void main(String[] args) {
Person p1=new Person("Tom",32,"548-56-4412");
Person p2=new Person("Jerry",60,"456-74-4125");
Person p3=new Person("Sherry",38,"418-55-1235");
Order order1=new Order();
order1.insertOrder(new String[]{"mouse","car charger"});
Order order2=new Order();
order2.insertOrder(new String[]{"Multi vitamin"});
Order order3=new Order();
order3.insertOrder(new String[]{"handbag", "iPod"});
Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
hashtable.put(p1,order1);
hashtable.put(p2,order2);
hashtable.put(p3,order3);
//The line below will fail if Person class does not override hashCode()
Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
for(String item:tomOrder.items)
{
System.out.println(item);
}
}
}
私見、それはルールが言うようにです - 2つのオブジェクトが等しいならば、それらは同じハッシュを持つべきです、すなわち、等しいオブジェクトは等しいハッシュ値を生成するべきです。
上記のように、Objectのデフォルトのequals()はアドレスの比較を行う==です。hashCode()は整数のアドレス(実際のアドレスのハッシュ)を返します。これは異なるObjectに対しても異なります。
Hashベースのコレクションでカスタムオブジェクトを使用する必要がある場合は、equals()とhashCode()の両方をオーバーライドする必要があります。たとえば、EmployeeオブジェクトのHashSetを維持したい場合、それより強いhashCodeとequalsを使用しない場合2つの異なるEmployeeオブジェクトをオーバーライドしてしまう可能性があります。これは、年齢をhashCode()として使用するときに発生しますが、Employee IDになる可能性がある一意の値を使用する必要があります。
hashCode()
メソッドは与えられたオブジェクトのためのユニークな整数を得るために使われます。このオブジェクトがHashTable
、HashMap
のようなデータ構造に格納される必要があるとき、この整数はバケット位置を決定するために使用されます。デフォルトでは、ObjectのhashCode()
メソッドは、オブジェクトが格納されているメモリアドレスの整数表現を返します。
オブジェクトのhashCode()
メソッドは、それらをHashTable
、HashMap
またはHashSet
に挿入するときに使用されます。参考のためにウィキペディアのHashTables
に関する詳細情報。
マップデータ構造にエントリを挿入するには、キーと値の両方が必要です。キーと値の両方がユーザー定義のデータ型である場合、キーのhashCode()
はオブジェクトを内部的に格納する場所を決定します。マップからもオブジェクトを検索する必要がある場合は、キーのハッシュコードによってオブジェクトの検索場所が決まります。
ハッシュコードは内部的に特定の "エリア"(またはリスト、バケットなど)を指すだけです。異なるキーオブジェクトが潜在的に同じハッシュコードを持つ可能性があるため、ハッシュコード自体が正しいキーが見つかることを保証するものではありません。次にHashTable
はこの領域(同じハッシュコードを持つすべてのキー)を繰り返し、そのキーのequals()
メソッドを使って正しいキーを見つけます。正しいキーが見つかると、そのキーに格納されているオブジェクトが返されます。
そのため、ご覧のとおり、HashTable
でオブジェクトを格納したり検索したりするときには、hashCode()
メソッドとequals()
メソッドの組み合わせが使用されます。
ノート:
hashCode()
とequals()
の両方を生成するには、常にオブジェクトの同じ属性を使用してください。私たちの場合のように、私たちは従業員IDを使いました。
equals()
は一貫している必要があります(オブジェクトが変更されていない場合は、同じ値を返し続けなければなりません)。
a.equals(b)
の場合は、a.hashCode()
はb.hashCode()
と同じでなければなりません。
一方を上書きする場合は、もう一方を上書きする必要があります。
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
Hashcodeは常に数字を返すので、アルファベットのキーではなく数字を使ってオブジェクトを取得するのが常に速いです。 どうする? 他のオブジェクトですでに利用可能な値を渡して新しいオブジェクトを作成したとします。渡された値が同じであるため、新しいオブジェクトは別のオブジェクトと同じハッシュ値を返します。同じハッシュ値が返されると、JVMは毎回同じメモリアドレスにアクセスし、同じハッシュ値に対して複数のオブジェクトが存在する場合は、equals()メソッドを使用して正しいオブジェクトを識別します。
public class Employee {
private int empId;
private String empName;
public Employee(int empId, String empName) {
super();
this.empId = empId;
this.empName = empName;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + "]";
}
@Override
public int hashCode() {
return empId + empName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(this instanceof Employee)) {
return false;
}
Employee emp = (Employee) obj;
return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
}
}
テストクラス
public class Test {
public static void main(String[] args) {
Employee emp1 = new Employee(101,"Manash");
Employee emp2 = new Employee(101,"Manash");
Employee emp3 = new Employee(103,"Ranjan");
System.out.println(emp1.hashCode());
System.out.println(emp2.hashCode());
System.out.println(emp1.equals(emp2));
System.out.println(emp1.equals(emp3));
}
}
Objectクラスではequals(Object obj)を使用してアドレス比較を比較します。これは、Testクラスで2つのオブジェクトを比較する場合はequalsメソッドがfalseになりますが、hashcode()をオーバーライドすると内容を比較して正しい結果を得るためです。
カスタムオブジェクトをMapのキーとして格納および取得したい場合は、カスタムオブジェクトのequalsとhashCodeを常にオーバーライドする必要があります。例えば:
Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");
ここで、p1とp2は1つのオブジェクトのみと見なし、map
のサイズは1になります。これらは等しいからです。