Geçersiz kılınırken hangi konular / tuzaklar dikkate alınmalı equals
ve hashCode
?
Geçersiz kılınırken hangi konular / tuzaklar dikkate alınmalı equals
ve hashCode
?
Yanıtlar:
equals()
( javadoc ) bir denklik ilişkisi tanımlamalıdır ( refleksif , simetrik ve geçişli olmalıdır ). Ayrıca, tutarlı olmalıdır (nesneler değiştirilmediyse, aynı değeri döndürmeye devam etmelidir). Ayrıca, o.equals(null)
her zaman yanlış döndürmelidir.
hashCode()
( javadoc ) da tutarlı olmalıdır (nesne açısından değiştirilmezse equals()
, aynı değeri döndürmeye devam etmelidir).
İlişki iki yöntem arasındadır:
Ne zaman olursa
a.equals(b)
, oa.hashCode()
zaman aynı olmalıdırb.hashCode()
.
Birini geçersiz kılarsanız, diğerini geçersiz kılmalısınız.
Hesaplamak equals()
için hesaplamak için kullandığınız aynı alan kümesini kullanın hashCode()
.
Mükemmel yardımcı sınıflar kullanın EqualsBuilder ve HashCodeBuilder gelen Apache Commons Lang kütüphanesine. Bir örnek:
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 veya WeakHashMap gibi karma tabanlı bir Koleksiyon veya Harita kullanırken , koleksiyona koyduğunuz anahtar nesnelerin hashCode () öğesinin nesne koleksiyondayken asla değişmediğinden emin olun. Bunu sağlamanın kurşun geçirmez yolu, anahtarlarınızı değiştirilemez hale getirmektir, bu da başka faydaları da vardır .
instanceof
İlk işleneni null olursa false döndüren ilk null denetimi gerekli değildir (yine Etkili Java).
Hazırda Bekleme gibi bir Nesne İlişkisi Eşleştiricisi (ORM) kullanarak devam eden sınıflarla uğraşıyorsanız, bunun zaten mantıksız derecede karmaşık olduğunu düşünmüyorsanız, dikkat çekmeye değer bazı sorunlar var!
Tembel yüklü nesneler alt sınıflardır
Nesneleriniz bir ORM kullanarak kalıcısa, çoğu durumda nesneyi veri deposundan çok erken yüklemekten kaçınmak için dinamik proxy'lerle uğraşacaksınız. Bu proxy'ler kendi sınıfınızın alt sınıfları olarak uygulanır. Bu this.getClass() == o.getClass()
geri döneceği anlamına gelir false
. Örneğin:
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
Bir ORM ile uğraşıyorsanız, o instanceof Person
doğru davranacak tek şey kullanmaktır.
Tembel yüklenen nesnelerde boş alanlar var
ORM'ler genellikle tembel yüklü nesnelerin yüklenmesini zorlamak için alıcıları kullanır. Bu araçlar person.name
olacak null
eğer person
, yüklenen tembel olsa bile person.getName()
güçleri yükleme ve iadeler "John Doe". Benim deneyimlerime göre, bu daha sık bitkileri kadar hashCode()
ve equals()
.
Bir ORM beraberler uğraşan Eğer varsa, her zaman alıcılar ve asla saha referansları kullandığınızdan emin olun hashCode()
ve equals()
.
Bir nesneyi kaydetmek durumunu değiştirir
Kalıcı nesneler genellikle id
nesnenin anahtarını tutmak için bir alan kullanır. Bir nesne ilk kaydedildiğinde bu alan otomatik olarak güncellenir. Alanında kimlik alanı kullanmayın hashCode()
. Ama içinde kullanabilirsiniz equals()
.
Sık kullandığım bir desen
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Ama: içeremez getId()
içinde hashCode()
. Bunu yaparsanız, bir nesne devam ettiğinde hashCode
değişir. Nesne bir içindeyse, bir HashSet
daha asla bulamazsınız.
Benim içinde Person
örneğin, muhtemelen kullanacağı getName()
için hashCode
ve getId()
artı getName()
için (sadece paranoya için) equals()
. Bazı "çarpışma" riski varsa hashCode()
, ama asla iyi değil equals()
.
hashCode()
değişmeyen özellik alt kümesini equals()
Saving an object will change it's state
! hashCode
dönmeli int
, nasıl kullanacaksın getName()
? Senin için bir örnek verebilir misinhashCode
Hakkında bir açıklama obj.getClass() != getClass()
.
Bu ifade, equals()
mirasın düşmanca olmasının sonucudur . JLS (Java dil belirtimi), o A.equals(B) == true
zaman B.equals(A)
dönmesi gerektiğini de belirtir true
. Bu ifadeyi geçersiz kılan equals()
(ve davranışını değiştiren) sınıfları devralmayı atlarsanız, bu belirtim bozulur.
İfade atlandığında ne olacağına ilişkin aşağıdaki örneği düşünün:
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));
}
}
Yapma new A(1).equals(new A(1))
Ayrıca, new B(1,1).equals(new B(1,1))
olması gerektiği gibi, gerçek vermeye sonuçlanır.
Bu çok iyi görünüyor, ancak her iki sınıfı da kullanmaya çalışırsak ne olur bakalım:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Açıkçası bu yanlış.
Simetrik koşulu sağlamak istiyorsanız. a = b ise b = a ve Liskov ikame ilkesi super.equals(other)
yalnızca B
örnek olması durumunda değil, örneğin sonra da kontrol eder 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;
Hangi çıktı:
a.equals(b) == true;
b.equals(a) == true;
Burada, eğer a
bir referans değilse B
, o zaman sınıfın bir referansı olabilir A
(çünkü onu genişletirsiniz), bu durumda siz super.equals()
de ararsınız .
ThingWithOptionSetA
bir eşit olabilir Thing
tüm ekstra seçenekler varsayılan değerlere sahip ve aynı şekilde bir için şartıyla ThingWithOptionSetB
bir için, o zaman mümkün olmalıdır ThingWithOptionSetA
eşit karşılaştırmak üzere bir ThingWithOptionSetB
hem tüm nesneleri olmayan baz özellikleri onların varsayılan eşleşen eğer ama Bunu nasıl test ettiğinizi anlamıyorum.
B b2 = new B(1,99)
o zaman, b.equals(a) == true
ve a.equals(b2) == true
fakat b.equals(b2) == false
.
Kalıtım dostu bir uygulama için, Tal Cohen'in equals () yöntemini nasıl doğru bir şekilde uygularım ?
Özet:
Etkili Java Programlama Dili Kılavuzu (Addison-Wesley, 2001) adlı kitabında Joshua Bloch, "Eşit bir sözleşmeyi genişletmenin ve eşit bir sözleşmeyi korurken bir özellik eklemenin hiçbir yolu olmadığını" iddia ediyor. Tal aynı fikirde değil.
Onun çözümü, simetrik olmayan bir körü körüne her iki şekilde çağırarak equals () yöntemini uygulamaktır. blindlyEquals (), alt sınıflar tarafından geçersiz kılınır, equals () devralınır ve hiçbir zaman geçersiz kılınmaz.
Misal:
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 İkame İlkesi karşılanacaksa , equals () işlevinin miras hiyerarşileri üzerinde çalışması gerektiğini unutmayın .
if (this.getClass() != o.getClass()) return false
ancak esnektir, çünkü türetilmiş sınıf (lar) eşittirse uğraştığında false değerini döndürür. Bu doğru mu?
Hala hiçbiri bunun için guava kütüphanesini tavsiye etmediğini şaşırttı.
//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
içinde this.getDate()
hiçbir şey ifade etmiyor (dağınıklık dışında)
if (!(otherObject instanceof DateAndPattern)) {
. Fıtık ve Steve Kuo ile anlaşın (bu kişisel tercih meselesi olsa da), ancak yine de +1.
Süper sınıfta java.lang.Object olarak iki yöntem vardır. Onları özel nesneye geçersiz kılmalıyız.
public boolean equals(Object obj)
public int hashCode()
Eşit nesneler, eşit oldukları sürece aynı karma kodunu üretmelidir, ancak eşit olmayan nesnelerin farklı karma kodları üretmesi gerekmez.
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
}
Daha fazlasını edinmek istiyorsanız, lütfen bu bağlantıyı http://www.javaranch.com/journal/2002/10/equalhash.html olarak kontrol edin.
Bu başka bir örnektir, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
İyi eğlenceler! @ @.
Üye eşitliğini kontrol etmeden önce sınıf eşitliğini kontrol etmenin birkaç yolu vardır ve her ikisinin de doğru koşullarda yararlı olduğunu düşünüyorum.
instanceof
Operatörü kullanın .this.getClass().equals(that.getClass())
.final
Eşit bir uygulamada veya eşitler için bir algoritma reçete eden bir arabirim uygularken # 1 kullanıyorum ( java.util
koleksiyon arabirimleri gibi - kontrol etmenin doğru yolu (obj instanceof Set)
veya uyguladığınız arabirim gibi). Eşit değerlerin geçersiz kılınabilmesi genellikle kötü bir seçimdir, çünkü bu simetri özelliğini bozar.
Seçenek # 2, eşitliği veya simetriyi bozmadan sınıfın güvenli bir şekilde genişletilmesini sağlar.
Sınıfınız da Comparable
ise equals
ve compareTo
yöntemleri de tutarlı olmalıdır. İşte bir Comparable
sınıftaki eşittir yöntemi için bir şablon :
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
için compareTo()
yöntem geçersiz kılındıysa, alt sınıf ve üst sınıf örnekleri eşit kabul edilmemelidir. Bu nesneler bir ağaçta birlikte kullanıldığında, bir instanceof
uygulamaya göre "eşit" olan anahtarlar katlanamayabilir.
Eşittir için içine bakmak Eşitlerin Sırlar tarafından Angelika Langer . Çok seviyorum. Ayrıca Java'daki Generics hakkında harika bir SSS . Diğer makalelerini burada ("Core Java" ya doğru kaydırın) ve Part-2 ve "karışık tip karşılaştırması" ile devam ediyor. Onları okurken eğlenin!
iki nesnenin eşitliğini belirlemek için equals () yöntemi kullanılır.
int değeri 10 her zaman 10'a eşittir. Ancak bu equals () yöntemi iki nesnenin eşitliği ile ilgilidir. Nesne dediğimizde, özellikleri olacaktır. Eşitlik konusunda karar vermek için bu özellikler dikkate alınır. Eşitliği belirlemek için tüm özelliklerin göz önünde bulundurulması gerekli değildir ve sınıf tanımına ve bağlamına göre karar verilebilir. Sonra equals () yöntemi geçersiz kılınabilir.
equals () yöntemini geçersiz kıldığımızda her zaman hashCode () yöntemini geçersiz kılmalıyız. Değilse ne olacak? Uygulamamızda hashtables kullanırsak, beklendiği gibi davranmaz. HashCode, depolanan değerlerin eşitliğini belirlemede kullanıldığından, bir anahtar için doğru karşılık gelen değeri döndürmez.
Verilen varsayılan uygulama Object sınıfındaki hashCode () yöntemidir, nesnenin dahili adresini kullanır ve onu tamsayıya dönüştürür ve döndürür.
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;
}
}
Örnek Kod Çıkışı:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Bulduğum bir gotcha, iki nesnenin birbirine referanslar içerdiği yerdir (bir örnek, tüm çocukları almak için ebeveyn üzerinde bir kolaylık yöntemi ile bir ebeveyn / çocuk ilişkisi).
Bu tür şeyler, örneğin Hazırda Bekletme eşleştirmeleri yaparken oldukça yaygındır.
HashCode'unuza ilişkinin her iki ucunu da eklerseniz veya eşittir sınaması yaparsanız, bir StackOverflowException ile biten yinelemeli bir döngüye girmeniz mümkündür.
En basit çözüm yöntemlere getChildren koleksiyonunu dahil etmemektir.
equals()
. Çılgın bir bilim adamı benim bir kopyamı yaratsaydı, eşdeğer olurduk. Ama aynı babamız olmazdı.