最近、このDeveloper Worksドキュメントを読みました 。
このドキュメントはすべてhashCode()
、equals()
効果的かつ正確に定義することを目的としていますが、これら2つのメソッドをオーバーライドする必要がある理由を理解できません。
これらの方法を効率的に実装するための決定をするにはどうすればよいですか?
最近、このDeveloper Worksドキュメントを読みました 。
このドキュメントはすべてhashCode()
、equals()
効果的かつ正確に定義することを目的としていますが、これら2つのメソッドをオーバーライドする必要がある理由を理解できません。
これらの方法を効率的に実装するための決定をするにはどうすればよいですか?
回答:
Joshua Blochは効果的なJavaについて語っています
equals()をオーバーライドするすべてのクラスで、hashCode()をオーバーライドする必要があります。そうしないと、Object.hashCode()の一般規約に違反し、クラスがHashMap、HashSet、Hashtableなどのすべてのハッシュベースのコレクションと連携して機能しなくなります。
オーバーライドequals()
せずにオーバーライドhashCode()
してを使用しようとするとどうなるかを例にして、それを理解してみましょうMap
。
このようなクラスがあり、2つのオブジェクトMyClass
が等しい場合(それら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()
が、2つのオブジェクトMyClass
が等しい場合importantField
は等しいが、オーバーライドしない場合は、この特定のケースで何が起こるかを見てみましょうequals()
。
オーバーライドのみ hashCode
あなたがこれを持っていると想像してください
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
オーバーライドするだけの場合hashCode
、myMap.put(first,someValue)
最初に呼び出すと、それが計算hashCode
され、指定されたバケットに格納されます。次に、呼び出すとき、それらは等しい(ビジネス要件による)ためmyMap.put(second,someOtherValue)
、マップドキュメントに従って最初から2番目に置き換える必要があります。
しかし、問題は、マップのハッシュそうするとき、等号が再定義されていなかったということであるsecond
オブジェクトが存在する場合にはバケツを通って、繰り返し処理が探してk
いるようにsecond.equals(k)
、それがどのように見つけることができません真実であるsecond.equals(first)
だろうfalse
。
それが明確だったことを願っています
if you think you need to override one, then you need to override both of them
間違っている。hashCode
クラスがオーバーライドするequals
が、逆は当てはまらない場合は、オーバーライドする必要があります。
equals
のjavadocのスペルアウト契約に違反Object
:「2つのオブジェクトが等しいに応じている場合はequals(Object)
、その後の呼び出し、方法hashCode
。同じ整数の結果を生成しなければならない2つのオブジェクトのそれぞれの方法を」もちろん、すべての契約のすべての部分がすべてのコードで実行されるわけではありませんが、正式に言えば、これは違反であり、発生するのを待っているバグであると考えます。
などのコレクションHashMap
とは、HashSet
使用するハッシュコードがコレクション内に格納されるべきである、と方法を決定するためにオブジェクトの値をハッシュコードは、そのコレクション内のオブジェクトを見つけるために再び使用されます。
ハッシュ検索は2段階のプロセスです。
hashCode()
)equals()
)ここでは、書き換えてすべき理由には小さな一例ですequals()
とhashcode()
。
Employee
年齢と名前の2つのフィールドを持つクラスを考えます。
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つのオブジェクトが等しいと見なされた場合、それらのハッシュコードも等しい必要があるのはなぜでしょうか。それ以外の場合、クラスObjectのデフォルトのハッシュコードメソッドはequals()
、2つ以上のオブジェクトが等しいと見なされるようにメソッドがオーバーライドされている場合でも、実質的に常に各オブジェクトに一意の番号を付けるため、オブジェクトを見つけることができません。
。ハッシュコードがそれを反映していなければ、オブジェクトがどれほど等しいかは関係ありません。したがって、もう一度:2つのオブジェクトが等しい場合、それらの
ハッシュコードも等しくなければなりません。
equals()をオーバーライドするすべてのクラスで、hashCode()をオーバーライドする必要があります。そうしないと、Object.hashCode()の一般規約に違反し、クラスがHashMap、HashSet、Hashtableなどのすべてのハッシュベースのコレクションと連携して機能しなくなります。
Joshua Bloch著 、Effective Javaから
定義することequals()
とhashCode()
一貫して、あなたはハッシュベースのコレクションのキーとしてあなたのクラスのユーザビリティを向上させることができます。hashCodeのAPIドキュメントで説明されているように、「このメソッドは、によって提供されるハッシュテーブルなどの利点のためにサポートされていjava.util.Hashtable
ます。」
これらのメソッドを効率的に実装する方法についての質問に対する最良の答えは、Effective Javaの第3章を読むことをお勧めします。
hashCode()
。
簡単に言えば、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");
イコールをオーバーライドしないと、次のようになります。
正しい?たぶん、これがあなたが望むものなら。ただし、2つの異なるインスタンスであるかどうかに関係なく、同じIDのオブジェクトを同じオブジェクトにしたいとします。イコール(およびハッシュコード)をオーバーライドします。
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();
}
}
イコールとハッシュコードの実装に関しては、グアバのヘルパーメソッドを使用することをお勧めします
アイデンティティは平等ではありません。
==
テスト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
上記のような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を検索する必要がある場合は、配列全体を反復処理する必要がありますが、グループ化すると、反復の範囲が制限されるため、速度が向上します。上記のすべての情報を格納するために使用されるデータ構造は、単純化のために2D配列と考えることができます
上記のハッシュマップとは別に、重複が追加されないこともわかります。これが、equalsとhashcodeをオーバーライドする必要がある主な理由です。
したがって、hashmapの内部動作を説明すると言ったとき、私たちはハッシュマップがどのようなメソッドを持っているか、そして私が上で説明した上記のルールに従う方法を見つける必要があります
したがって、ハッシュマップには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
、オブジェクトごとに常にオブジェクトクラスとしてnew を返すからです。
equals()
:
equalメソッドのみをオーバーライドする場合、a.equals(b)
trueはhashCode
、aとbのaが同じでなければならないが発生しないことを意味します。hashCode
メソッドをオーバーライドしなかったため。
注: hashCode()
Objectクラスのメソッドは、常にhashCode
各オブジェクトに対してnew を返します。
したがって、ハッシュベースのコレクションでオブジェクトを使用する必要がある場合は、equals()
との両方をオーバーライドする必要がありますhashCode()
。
Javaは、
「2つのオブジェクトがObjectクラスのequalsメソッドを使用して等しい場合、ハッシュコードメソッドはこれら2つのオブジェクトに同じ値を与える必要があります。」
したがって、クラスでオーバーライドequals()
する場合は、hashcode()
このルールに従うためにメソッドもオーバーライドする必要があります。両方の方法、equals()
及びはhashcode()
、で使用されるHashtable
キーと値のペアのような値を格納するために、例えば、。一方をオーバーライドして他方をオーバーライドすると、そのHashtable
ようなオブジェクトをキーとして使用した場合に、意図したとおりに機能しない可能性があります。
それらをオーバーライドしない場合は、Objectのデフォルトインプリメンテーションを使用するためです。
インスタンスの等価性とhascode値は、通常、オブジェクトを構成するものの知識を必要とするため、具体的な意味を持たせるために、クラスで再定義する必要があります。
@Lomboの回答に追加する
いつequals()をオーバーライドする必要がありますか?
Objectのequals()のデフォルトの実装は
public boolean equals(Object obj) {
return (this == obj);
}
これは、2つのオブジェクトが同じメモリアドレスを持つ場合にのみ等しいと見なされることを意味します。これは、オブジェクトをそれ自体と比較する場合にのみ当てはまります。
ただし、1つまたは複数のプロパティの値が同じである場合、2つのオブジェクトを同じと見なすこともできます(@Lomboの回答にある例を参照してください)。
したがってequals()
、これらの状況ではオーバーライドし、独自の平等条件を与えることになります。
equals()の実装に成功し、それがうまく機能しているのに、なぜhashCode()もオーバーライドするよう求めているのですか?
ユーザー定義クラスで「ハッシュ」ベースのコレクションを使用しない限り、問題ありません。しかし、将来のある時点で、あなたは使用することがありますHashMap
かHashSet
、あなたはそうではない場合override
や)(ハッシュコードを「正しく実装」、これらのハッシュベースのコレクションが意図したとおりに動作しません。
等号のみオーバーライド(@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は、hashCodeがsecond
同じかどうかをチェックしますfirst
ます。値が同じである場合にのみ、同じバケット内の等価性のチェックに進みます。
ただし、ここでは、これら2つのオブジェクトのhashCodeが異なります(デフォルトの実装とは異なるメモリアドレスを持っているため)。したがって、同等性をチェックすることさえ気にしません。
オーバーライドされた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が存在する場合、バケット1に存在する必要があることを識別します。したがって、バケット1のみが検索されます!!
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()
メソッドを使用して、値がすでにバケットに存在するかどうかを確認します。そうでない場合は追加され、それ以外の場合は現在の値に置き換えられますhashCode()
、最初にエントリ(バケット)を見つけ、エントリequals()
内の値を見つけるために使用します
両方がオーバーライドされている場合、
地図< A >
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
等しいがオーバーライドされない場合
地図< 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...
HashCode Equal契約
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();
}
値オブジェクトを使用するときに役立ちます。以下は、Portland Pattern Repositoryからの抜粋です。
値オブジェクトの例は、数値、日付、金額、文字列などです。通常、これらは非常に広く使用されている小さなオブジェクトです。それらのアイデンティティは、オブジェクトのアイデンティティではなく状態に基づいています。このようにして、同じ概念値オブジェクトの複数のコピーを持つことができます。
したがって、1998年1月16日の日付を表すオブジェクトのコピーを複数持つことができます。これらのコピーはすべて互いに等しくなります。このような小さなオブジェクトの場合、日付を表すために単一のオブジェクトに依存するよりも、新しいオブジェクトを作成して移動する方が簡単なことがよくあります。
値オブジェクトは、常にJava(またはSmalltalkでは=)の.equals()をオーバーライドする必要があります。(.hashCode()もオーバーライドすることを忘れないでください。)
equalsメソッドとhashcodeはオブジェクトクラスで定義されます。デフォルトでは、equalsメソッドがtrueを返す場合、システムはさらに進んでハッシュコードの値をチェックします。2つのオブジェクトのハッシュコードも同じ場合のみ、オブジェクトは同じと見なされます。したがって、equalsメソッドのみをオーバーライドする場合、オーバーライドされたequalsメソッドは2つのオブジェクトが等しいことを示していても、システム定義のハッシュコードは2つのオブジェクトが等しいことを示さない場合があります。したがって、ハッシュコードもオーバーライドする必要があります。
true
ものequals
が一致すると見なされないという保証はありません。一方、コレクションが発生しても、同じハッシュコードを使用できないことに気づいたとしても、それらが同じであることには気付かないでしょう。
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について、y.equals(x)がtrueを返す場合のみ、x.equals(y)はtrueを返します。
それは推移的です:すべての参照値x、y、zについて、x.equals(y)がtrueを返し、y.equals(z)がtrueを返す場合、x.equals(z)はtrueを返します。
これは一貫しています。オブジェクトの等値比較で使用される情報が変更されていない限り、x.equals(y)の複数の呼び出しは常にtrueまたは一貫してfalseを返します。
null以外の参照値xの場合、x.equals(null)はfalseを返す必要があります。
public int hashCode()
このメソッドは、このメソッドが呼び出されたオブジェクトのハッシュコード値を返します。このメソッドは、ハッシュコード値を整数として返し、Hashtable、HashMap、HashSetなどのハッシュベースのコレクションクラスのためにサポートされています。このメソッドは、equalsメソッドをオーバーライドするすべてのクラスでオーバーライドする必要があります。
hashCodeの一般的な規約は次のとおりです。
Javaアプリケーションの実行中に同じオブジェクトで2回以上呼び出される場合、オブジェクトの等値比較で使用される情報が変更されていなければ、hashCodeメソッドは常に同じ整数を返す必要があります。
この整数は、アプリケーションのある実行から同じアプリケーションの別の実行まで一貫性を保つ必要はありません。
equals(Object)メソッドに従って2つのオブジェクトが等しい場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、同じ整数の結果が生成される必要があります。
equals(java.lang.Object)メソッドに従って2つのオブジェクトが等しくない場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、異なる整数の結果が生成される必要はありません。ただし、プログラマは、異なるオブジェクトに対して異なる整数の結果を生成すると、ハッシュテーブルのパフォーマンスが向上する可能性があることに注意する必要があります。
等しいオブジェクトは等しい限り同じハッシュコードを生成する必要がありますが、等しくないオブジェクトは異なるハッシュコードを生成する必要はありません。
リソース:
以下の例では、Personクラスのequalsまたはhashcodeのオーバーライドをコメント化すると、このコードはトムの注文の検索に失敗します。ハッシュコードのデフォルトの実装を使用すると、ハッシュテーブル検索でエラーが発生する可能性があります。
以下に示すのは、Personによる人々の注文を引き上げる簡略化されたコードです。人はハッシュテーブルのキーとして使用されています。
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);
}
}
}
文字列クラスとラッパークラスの実装が異なりequals()
、hashCode()
、Objectクラスメソッドます。Objectクラスのequals()メソッドは、内容ではなくオブジェクトの参照を比較します。ObjectクラスのhashCode()メソッドは、内容が同じであるかどうかに関係なく、すべてのオブジェクトに対して個別のハッシュコードを返します。
Mapコレクションを使用していて、キーが永続タイプであるStringBuffer / builderタイプである場合、問題が発生します。Stringクラスとは異なり、equals()とhashCode()をオーバーライドしないため、2つの異なるオブジェクトを比較すると、両方が同じ内容であっても、equals()はfalseを返します。同じコンテンツキーを格納するhashMapを作成します。同じコンテンツキーを保存すると、マップでは重複するキーがまったく許可されないため、マップのルールに違反します。したがって、クラスのequals()およびhashCode()メソッドをオーバーライドし、実装を提供して(IDEはこれらのメソッドを生成できます)、これらがStringのequals()およびhashCode()と同じように機能し、同じコンテンツキーを防止します。
equals()はハッシュコードに従って機能するため、equals()とともにhashCode()メソッドをオーバーライドする必要があります。
さらに、equals()とともにhashCode()メソッドをオーバーライドすると、equals()-hashCode()コントラクトに影響を与えません:「2つのオブジェクトが等しい場合、それらは同じハッシュコードを持つ必要があります。」
hashCode()のカスタム実装を作成する必要があるのはいつですか?
私たちが知っているように、HashMapの内部処理はハッシュの原則に基づいています。エントリセットが格納される特定のバケットがあります。同じカテゴリオブジェクトを同じインデックスに格納できるように、必要に応じてhashCode()実装をカスタマイズします。put(k,v)
メソッドを使用してMapコレクションに値を格納する場合、put()の内部実装は次のとおりです。
put(k, v){
hash(k);
index=hash & (n-1);
}
つまり、インデックスを生成し、特定のキーオブジェクトのハッシュコードに基づいてインデックスが生成されます。したがって、同じハッシュコードエントリセットが同じバケットまたはインデックスに格納されるため、このメソッドで要件に応じたハッシュコードを生成します。
それでおしまい!
hashCode()
メソッドは、特定のオブジェクトの一意の整数を取得するために使用されます。この整数は、このオブジェクトがいくつかに格納する必要がある場合、バケットの場所を決定するために使用されHashTable
、HashMap
データ構造などが含まれます。デフォルトでは、ObjectのhashCode()
メソッドはオブジェクトが格納されているメモリアドレスの整数表現を返します。
hashCode()
オブジェクトのメソッドは、オブジェクトをHashTable
、HashMap
またはに挿入するときに使用されますHashSet
。HashTables
Wikipedia.orgで詳細を参照してください。
マップデータ構造にエントリを挿入するには、キーと値の両方が必要です。キーと値の両方がユーザー定義のデータ型である場合、キーのhashCode()
はオブジェクトを内部的に格納する場所を決定します。マップからオブジェクトを検索する必要がある場合も、キーのハッシュコードによって、オブジェクトを検索する場所が決定されます。
ハッシュコードは、内部的に特定の「領域」(またはリスト、バケットなど)のみを指します。異なるキーオブジェクトが同じハッシュコードを持つ可能性があるため、ハッシュコード自体は正しいキーが見つかる保証はありません。次にHashTable
、この領域(同じハッシュコードを持つすべてのキー)を反復処理し、キーのequals()
メソッドを使用して適切なキーを見つけます。正しいキーが見つかると、そのキーに格納されているオブジェクトが返されます。
ご覧のとおり、hashCode()
との組み合わせは、でequals()
オブジェクトを保存したり検索したりするときに使用されますHashTable
。
ノート:
生成するオブジェクトhashCode()
とequals()
その両方の属性は常に同じにします。私たちの場合と同様に、従業員IDを使用しました。
equals()
一貫している必要があります(オブジェクトが変更されない場合は、同じ値を返し続ける必要があります)。
いつでもa.equals(b)
、a.hashCode()
と同じでなければなりませんb.hashCode()
。
一方をオーバーライドする場合は、もう一方をオーバーライドする必要があります。
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
hashCode()
すべてのオブジェクトに一意の整数を返すために使用されません。それは不可能です。これは、4番目の段落の2番目の文で矛盾しています。
私見、それはルールによると-2つのオブジェクトが等しい場合、それらは同じハッシュを持つ必要があります。つまり、等しいオブジェクトは等しいハッシュ値を生成する必要があります。
上記のように、Objectのデフォルトのequals()は==であり、アドレスで比較を行います。hashCode()は、整数(実際のアドレスのハッシュ)でアドレスを返します。
ハッシュベースのコレクションでカスタムオブジェクトを使用する必要がある場合は、equals()とhashCode()の両方をオーバーライドする必要があります。例:従業員オブジェクトのHashSetを維持する場合、より強力なhashCodeとequalsを使用しない場合2つの異なるEmployeeオブジェクトを上書きしてしまう可能性があります。これは、年齢をhashCode()として使用するときに発生しますが、従業員IDである可能性のある一意の値を使用する必要があります。
ハッシュコードは常に数値を返すため、アルファベットキーではなく数値を使用してオブジェクトを取得するのが常に高速です。それはどうしますか?他のオブジェクトですでに利用可能な値を渡して新しいオブジェクトを作成したと仮定します。これで、渡された値が同じであるため、新しいオブジェクトは別のオブジェクトと同じハッシュ値を返します。同じハッシュ値が返されると、JVMは毎回同じメモリアドレスに移動し、同じハッシュ値に対して複数のオブジェクトが存在する場合は、equals()メソッドを使用して正しいオブジェクトを識別します。
カスタムオブジェクトを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のみになります。
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));
}
}
オブジェクトクラスでは、equals(Object obj)を使用してアドレス比較を比較します。Testクラスで2つのオブジェクトを比較した場合、equalsメソッドはfalseを返しますが、hashcode()をオーバーライドすると、コンテンツを比較して適切な結果を得ることができます。
オーバーライドしてequals()
、しない場合hashcode()
、あなたや他の誰かがのようなハッシュ化されたコレクションでそのクラス型を使用しない限り、問題は見つかりませんHashSet
。私の前の人々は文書化された理論を何度も明確に説明してきましたが、私は非常に単純な例を提供するためにここにいます。
equals()
カスタマイズされたものを意味する必要があるクラスを考えてみましょう:-
public class Rishav {
private String rshv;
public Rishav(String rshv) {
this.rshv = rshv;
}
/**
* @return the rshv
*/
public String getRshv() {
return rshv;
}
/**
* @param rshv the rshv to set
*/
public void setRshv(String rshv) {
this.rshv = rshv;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Rishav) {
obj = (Rishav) obj;
if (this.rshv.equals(((Rishav) obj).getRshv())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
@Override
public int hashCode() {
return rshv.hashCode();
}
}
次に、このメインクラスを検討します。
import java.util.HashSet;
import java.util.Set;
public class TestRishav {
public static void main(String[] args) {
Rishav rA = new Rishav("rishav");
Rishav rB = new Rishav("rishav");
System.out.println(rA.equals(rB));
System.out.println("-----------------------------------");
Set<Rishav> hashed = new HashSet<>();
hashed.add(rA);
System.out.println(hashed.contains(rB));
System.out.println("-----------------------------------");
hashed.add(rB);
System.out.println(hashed.size());
}
}
これにより、次の出力が生成されます。
true
-----------------------------------
true
-----------------------------------
1
私は結果に満足しています。しかし、私がオーバーライドしていない場合、同じメンバーのコンテンツhashCode()
をRishav
持つオブジェクトは、hashCode
デフォルトの動作で生成されるように、異なるものとして一意として扱われなくなるため、悪夢が発生します。これが出力されます:-
true
-----------------------------------
false
-----------------------------------
2
Bah-「equals()をオーバーライドするすべてのクラスで、hashCode()をオーバーライドする必要があります。」
[Joshua Bloch著、Effective Javaから?]
これは間違った方法ではありませんか?hashCodeのオーバーライドは、おそらくハッシュキークラスを記述していることを意味しますが、equalsのオーバーライドは確かにそうではありません。ハッシュキーとして使用されないクラスはたくさんありますが、他の理由で論理的等価性テストの方法が必要です。「等しい」を選択した場合は、このルールを過度に適用することにより、hashCode実装を作成するように求められる場合があります。達成できることは、テストされていないコードをコードベースに追加することです。これは、将来誰かをつまずかせるのを待っている悪です。また、必要のないコードを書くことはアンチアジャイルです。それはただ間違っています(そしてideが生成したものはおそらくあなたの手作りのイコールと互換性がありません)。
確かに、キーとして使用するために記述されたオブジェクトのインターフェイスを義務付けるべきだったのでしょうか。とにかく、ObjectがデフォルトのhashCode()とequals()imhoを提供してはいけません。それはおそらく多くの壊れたハッシュコレクションを奨励しています。
しかし、とにかく、「ルール」は前から後ろに書かれていると思います。その間、同等性のテスト方法に「等しい」を使用しないようにします:-(