回答:
equals()
(javadoc)は同値関係を定義する必要があります(再帰的、対称的、推移的でなければなりません)。さらに、一貫性がなければなりません(オブジェクトが変更されていない場合は、同じ値を返し続ける必要があります)。さらに、o.equals(null)
常にfalseを返す必要があります。
hashCode()
(javadoc)も一貫している必要があります(オブジェクトがに関して変更されていない場合equals()
、同じ値を返し続ける必要があります)。
2つの方法の関係は次のとおりです。
いつでも
a.equals(b)
、a.hashCode()
と同じでなければなりませんb.hashCode()
。
一方をオーバーライドする場合は、もう一方をオーバーライドする必要があります。
計算equals()
に使用するのと同じフィールドセットを使用して計算しhashCode()
ます。
Apache Commons Langライブラリーの優れたヘルパークラスであるEqualsBuilderおよびHashCodeBuilderを使用してください。例:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
HashSet、LinkedHashSet、HashMap、Hashtable、WeakHashMapなどのハッシュベースのコレクションまたはマップを使用する場合は、オブジェクトがコレクション内にある間、コレクションに入れたキーオブジェクトのhashCode()が変更されないようにしてください。これを確実にする完全な方法は、キーを不変にすることです。これには他の利点もあります。
instanceof
最初のオペランドがnullの場合にfalse を返すという事実を考えると、最初のnullチェックは必要ありません(再び有効なJava)。
HibernateのようなObject-Relationship Mapper(ORM)を使用して永続化されているクラスを扱っている場合、これがすでに不当に複雑であると思わなかった場合は、注目に値するいくつかの問題があります。
遅延読み込みされたオブジェクトはサブクラスです
オブジェクトがORMを使用して永続化されている場合、多くの場合、動的プロキシを処理して、データストアからオブジェクトが早くロードされないようにします。これらのプロキシは、独自のクラスのサブクラスとして実装されます。これは、this.getClass() == o.getClass()
が戻ることを意味しますfalse
。例えば:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
ORMを扱っている場合、o instanceof Person
正しく動作するのは使用することだけです。
遅延読み込みされたオブジェクトにはnullフィールドがあります
ORMは通常、ゲッターを使用して、遅延ロードされたオブジェクトを強制的にロードします。これは、ロードが強制されて"John Doe"が返された場合でも、遅延ロードされた場合であることperson.name
を意味します。私の経験では、これはとでより頻繁に発生します。null
person
person.getName()
hashCode()
equals()
ORMを扱う場合は、常にゲッターを使用し、とでフィールド参照を使用しないようにしhashCode()
てくださいequals()
。
オブジェクトを保存すると状態が変わります
永続オブジェクトは、id
フィールドを使用してオブジェクトのキーを保持することがよくあります。このフィールドは、オブジェクトが最初に保存されるときに自動的に更新されます。でidフィールドを使用しないでくださいhashCode()
。ただし、で使用できますequals()
。
私がよく使うパターンは
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
しかし:あなたは含めることはできませんgetId()
の中でhashCode()
。その場合、オブジェクトが永続化されると、そのオブジェクトがhashCode
変更されます。オブジェクトが内にある場合は、HashSet
二度と「見つかりません」。
私のPerson
例では、おそらくgetName()
for hashCode
とgetId()
plus getName()
(単なるパラノイアのために)を使用しequals()
ます。の「衝突」のリスクがある場合は問題ありhashCode()
ませんが、の場合は問題ありませんequals()
。
hashCode()
変更されないプロパティのサブセットを使用する必要があります equals()
Saving an object will change it's state
!hashCode
戻る必要int
があるので、どのように使用しgetName()
ますか?あなたのための例を与えることができますhashCode
についての説明obj.getClass() != getClass()
。
このステートメントは、equals()
継承が非友好的であることの結果です。JLS(Java言語仕様)であればあることを指定しA.equals(B) == true
、その後B.equals(A)
も返さなければなりませんtrue
。このステートメントを省略すると、オーバーライドするequals()
(およびその動作を変更する)クラスを継承するクラスは、この仕様に違反します。
ステートメントが省略された場合の次の例を考えてみます。
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
やってnew A(1).equals(new A(1))
も、new B(1,1).equals(new B(1,1))
それが必要として、真配るなります。
これはすべて非常によく見えますが、両方のクラスを使用しようとするとどうなるかを見てください。
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
明らかに、これは間違っています。
対称状態を確保したい場合。a = b(b = aの場合)とLiskov置換原理super.equals(other)
は、B
インスタンスの場合だけでなく、たとえば後でチェックしA
ます。
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
どちらが出力されます:
a.equals(b) == true;
b.equals(a) == true;
ここで、a
がの参照でない場合、それはB
クラスの参照である可能性がありますA
(拡張するため)。この場合は、も呼び出しsuper.equals()
ます。
ThingWithOptionSetA
に等しくすることができるThing
すべての余分なオプションはデフォルト値があり、同様にのためにすることを提供ThingWithOptionSetB
するために、それは可能なはずThingWithOptionSetA
に等しいとされるようにThingWithOptionSetB
両方のオブジェクトのすべての非塩基特性は、そのデフォルト値に一致する場合にのみ、しかし、どうやってテストするかわかりません。
B b2 = new B(1,99)
、その後、b.equals(a) == true
及びa.equals(b2) == true
けどb.equals(b2) == false
。
継承に適した実装については、Tal Cohenのソリューションをチェックしてください。equals()メソッドを正しく実装するにはどうすればよいですか?
概要:
Joshua Blochは、彼の著書「Effective Java Programming Language Guide(Addison-Wesley、2001)」で、「インスタンス化可能なクラスを拡張してアスペクトを追加しながらアスペクトを追加する方法はない」と述べています。タルはそう思わない。
彼の解決策は、別の非対称なblindlyEquals()を両方の方法で呼び出すことによってequals()を実装することです。blindlyEquals()はサブクラスによってオーバーライドされ、equals()は継承され、オーバーライドされることはありません。
例:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Liskov置換原則が満たされる場合、equals()は継承階層全体で機能する必要があることに注意してください。
if (this.getClass() != o.getClass()) return false
ていますが、派生クラスが等号を変更する必要がある場合にのみfalseを返すという点で柔軟性があります。そうですか?
まだ誰もがこのためにグアバライブラリを推奨していないことにまだ驚いています。
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
this
中this.getDate()
の手段(乱雑以外)何も
if (!(otherObject instanceof DateAndPattern)) {
。ヘルナンとスティーブクオに同意する(ただし、個人的な好みの問題です)が、それでも+1します。
スーパークラスには、java.lang.Objectとして2つのメソッドがあります。それらをカスタムオブジェクトにオーバーライドする必要があります。
public boolean equals(Object obj)
public int hashCode()
等しいオブジェクトは等しい限り同じハッシュコードを生成する必要がありますが、等しくないオブジェクトは異なるハッシュコードを生成する必要はありません。
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
さらに取得したい場合は、このリンクをhttp://www.javaranch.com/journal/2002/10/equalhash.htmlとして確認してください。
これは別の例です 。http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
楽しんで!@。@
メンバーの等価性をチェックする前にクラスの等価性をチェックする方法はいくつかありますが、どちらも適切な状況で役立ちます。
instanceof
演算子を使用します。this.getClass().equals(that.getClass())
ます。final
イコールの実装で#1を使用するか、イコールのアルゴリズムを規定するインターフェースを実装するときに(java.util
コレクションインターフェースのように、実装しているインターフェースで確認するための正しい方法(obj instanceof Set)
)。equalsがオーバーライドされる可能性がある場合、対称性のプロパティが壊れるので、一般的にこれは悪い選択です。
オプション#2を使用すると、equalsをオーバーライドしたり対称性を壊したりすることなく、クラスを安全に拡張できます。
クラスもであるComparable
場合、equals
およびcompareTo
メソッドも一貫している必要があります。Comparable
クラスのequalsメソッドのテンプレートは次のとおりです。
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
final
、compareTo()
メソッドがオーバーライドされてソート順が逆になる場合、サブクラスとスーパークラスのインスタンスは等しいと見なされるべきではありません。これらのオブジェクトをツリーで一緒に使用すると、instanceof
実装に応じて「等しい」キーが見つからない場合があります。
イコールについては、Angelika LangerによるSecrets of Equalsを参照してください。私はそれが大好きです。彼女はJavaのジェネリックスに関する素晴らしいFAQでもあります。彼女の他の記事をここに表示し(「コアJava」までスクロールして)、そこでパート2と「混合型の比較」も続けます。それらを読んで楽しんでください!
equals()メソッドは、2つのオブジェクトの等価性を判断するために使用されます。
10のint値は常に10に等しいためです。ただし、このequals()メソッドは、2つのオブジェクトのほぼ同等です。オブジェクトと言うと、プロパティがあります。同等性を決定するために、これらの特性が考慮されます。等価性を判断するためにすべてのプロパティを考慮する必要はなく、クラス定義とコンテキストに関して決定する必要があります。次に、equals()メソッドをオーバーライドできます。
equals()メソッドをオーバーライドする場合は、常にhashCode()メソッドをオーバーライドする必要があります。そうでない場合、どうなりますか?アプリケーションでハッシュテーブルを使用すると、期待どおりに動作しません。格納された値の同等性を判断するためにhashCodeが使用されるため、キーに対応する正しい値は返されません。
指定されたデフォルトの実装は、ObjectクラスのhashCode()メソッドです。オブジェクトの内部アドレスを使用し、それを整数に変換して返します。
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
コード出力例:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
私が見つけた1つの落とし穴は、2つのオブジェクトがお互いへの参照を含むところです(1つの例は、すべての子を取得するための親の便利なメソッドを持つ親/子関係です)。
この種のことは、たとえばHibernateマッピングを行う場合にはかなり一般的です。
関係の両端をhashCodeまたはequalsテストに含めると、StackOverflowExceptionで終了する再帰ループに入る可能性があります。
最も簡単な解決策は、メソッドにgetChildrenコレクションを含めないことです。
equals()
。マッドサイエンティストが私の複製を作成した場合、私たちは同等です。しかし、同じ父親はいないでしょう。