Java の基本クラス Object はいくつかのメソッドを提供します。その中で、equals() メソッドは 2 つのオブジェクトが等しいかどうかを判断するために使用され、hashCode() メソッドはオブジェクトのハッシュ コードを計算するために使用されます。 equals() も hashCode() も最終メソッドではないため、上書きできます。
#この記事では、2 つのメソッドを使用および書き換える際に注意が必要な問題をいくつか紹介します。
1.equal() メソッド
Object クラスのquals() メソッドは次のように実装されます:
public boolean equals(Object obj) { return (this == obj); }
この実装から、Object クラスの実装では最も高度な差別化を伴うアルゴリズムが採用されていることがわかります。つまり、2 つのオブジェクトが同じオブジェクトでない限り、equals() は false を返さなければなりません。 。
#クラスを定義するときに、equals() メソッドをオーバーライドできますが、いくつかの注意事項があります。JDK では、equals() メソッドを実装するときに従う必要がある規則について説明しています。 # (1) 再帰性: x.equals(x) は true を返さなければなりません。 (2) 対称性: x.equals(y) と y.equals(x) の戻り値は等しくなければなりません。 (3) 推移性: x.equals(y) が true、y.equals(z) も true の場合、x.equals(z) は true でなければなりません。 (4) 一貫性:equals() 内のオブジェクト x および y によって使用される情報が変更されていない場合、x.equals(y) の値は常に変更されません。 (5) 非 null: x が null でなく、y が null の場合、x.equals(y) は false でなければなりません。#2. hashCode() メソッド
1. オブジェクト hashCode()
#Object クラスの hashCode() メソッドは次のように宣言されています: public native int hashCode();
一般に、hashCode() は、HashSet、HashMap などのハッシュ テーブルで機能します。 オブジェクトをハッシュ テーブル (HashSet、HashMap など) に追加するとき、最初に hashCode() メソッドを呼び出して、オブジェクトのハッシュ コードを計算します。ハッシュ コードを通じて、次のことができます。ハッシュ テーブル内のオブジェクトを直接見つけます (通常、ハッシュ テーブルのサイズに対するハッシュ コードの係数)。この位置にオブジェクトがない場合は、この位置にオブジェクトを直接挿入できます。この位置にオブジェクトがある場合 (リンク リストを通じて実装され、複数ある場合があります)、equals() メソッドを呼び出して、次のいずれかを比較します。これらのオブジェクトはオブジェクトと等しいです。等しい場合はオブジェクトを保存する必要はありません。等しくない場合は、オブジェクトをリンク リストに追加します。
これは、equals() が等しい場合、hashCode() が等しくなければならない理由も説明しています。
2 つのオブジェクトquals() が等しい場合、それらはハッシュ テーブル (HashSet、HashMap など) に 1 回だけ出現する必要があります。hashCode() が等しくない場合、それらはハッシュ テーブル内の異なる場所にハッシュされます。ハッシュ テーブル。ハッシュ テーブル内で複数回出現する位置。 実際には、JVM では、ロードされたオブジェクトには、オブジェクト ヘッダー、インスタンス データ、および埋め込みという 3 つの部分がメモリ内に含まれています。このうち、オブジェクト ヘッダーにはオブジェクトの型へのポインタと MarkWord が含まれており、MarkWord にはオブジェクトの GC 世代情報とロック ステータス情報だけでなく、オブジェクトのハッシュコード 、オブジェクト インスタンスも含まれています。データはオブジェクトによって実際に格納されている有効な情報です。HotSpot ではオブジェクトの開始アドレスが 8 バイトの整数倍である必要があるため、パディング部分はプレースホルダーとしてのみ機能します。
3. String でのquals() と hashCode() の実装
private final char value[]; private int hash; // Default to 0 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
2. String クラスは、パフォーマンスを向上させるために hashCode() の結果をハッシュ値としてキャッシュします。
3. String オブジェクトquals() が等しい条件は、両方が String オブジェクトであり、長さが同じで、文字列値がまったく同じであることですが、同じオブジェクトである必要はありません。
4. String の hashCode() の計算式は、 s[0]*31^(n-1) s[1]*31^(n-2) ... s[n-1 ]
hashCode()の計算処理でなぜ31という数字が使われるのかについてですが、主な理由は以下のとおりです。
1、使用质数计算哈希码,由于质数的特性,它与其他数字相乘之后,计算结果唯一的概率更大,哈希冲突的概率更小。
2、使用的质数越大,哈希冲突的概率越小,但是计算的速度也越慢;31是哈希冲突和性能的折中,实际上是实验观测的结果。
3、JVM会自动对31进行优化:31 * i == (i << 5) - i
本节先介绍重写hashCode()方法应该遵守的原则,再介绍通用的hashCode()重写方法。
1、重写hashcode()的原则
通过前面的描述我们知道,重写hashCode需要遵守以下原则:
(1)如果重写了equals()方法,检查条件“两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等”是否成立,如果不成立,则重写hashCode ()方法。
(2)hashCode()方法不能太过简单,否则哈希冲突过多。
(3)hashCode()方法不能太过复杂,否则计算复杂度过高,影响性能。
2、hashCode()重写方法
《Effective Java》中提出了一种简单通用的hashCode算法
A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:
(1) 如果是boolean值,则计算f ? 1:0
(2) 如果是byte\char\short\int,则计算(int)f
(3) 如果是long值,则计算(int)(f ^ (f >>> 32))
(4) 如果是float值,则计算Float.floatToIntBits(f)
(5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
(6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
(7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。
C、最后,把每个域的散列码合并到对象的哈希码中。
下面通过一个例子进行说明。在该例中,Person类重写了equals()方法和hashCode()方法。因为equals()方法中只使用了name域和age域,所以hashCode()方法中,也只计算name域和age域。
对于String类型的name域,直接使用了String的hashCode()方法;对于int类型的age域,直接用其值作为该域的hash。
public class Person { private String name; private int age; private boolean gender; public Person() { super(); } 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; } public boolean isGender() { return gender; } public void setGender(boolean gender) { this.gender = gender; } @Override public boolean equals(Object another) { if (this == another) { return true; } if (another instanceof Person) { Person anotherPerson = (Person) another; if (this.getName().equals(anotherPerson.getName()) && this.getAge() == anotherPerson.getAge()) { return true; } else { return false; } } return false; } @Override public int hashCode() { int hash = 17; hash = hash * 31 + getName().hashCode(); hash = hash * 31 + getAge(); return hash; } }
推荐教程:java教程
以上がequals() メソッドと hashCode() メソッド (詳細な紹介)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。