จะต้องพิจารณาประเด็น / ข้อผิดพลาดใดบ้างเมื่อเอาชนะequalsและhashCode?
จะต้องพิจารณาประเด็น / ข้อผิดพลาดใดบ้างเมื่อเอาชนะequalsและhashCode?
คำตอบ:
equals()( Javadoc ) ต้องกำหนดความสมดุล (มันจะต้องสะท้อน , สมมาตรและสกรรมกริยา ) นอกจากนี้มันจะต้องสอดคล้องกัน (หากวัตถุไม่ได้ถูกแก้ไขแล้วมันจะต้องกลับมาเป็นค่าเดิม) นอกจากนี้o.equals(null)ต้องส่งคืนค่าเท็จเสมอ
hashCode()( javadoc ) จะต้องสอดคล้องกัน (หากวัตถุนั้นไม่ได้รับการแก้ไขในแง่ของequals()มันจะต้องส่งกลับค่าเดียวกัน)
ความสัมพันธ์ระหว่างสองวิธีคือ
เมื่อใดก็ตามที่
a.equals(b)แล้วจะต้องเป็นเช่นเดียวกับa.hashCode()b.hashCode()
หากคุณแทนที่หนึ่งแล้วคุณควรแทนที่อีก
ใช้ชุดเดียวกันของเขตข้อมูลที่คุณใช้ในการคำนวณการคำนวณequals()hashCode()
ใช้คลาสตัวช่วยที่ยอดเยี่ยมEqualsBuilderและHashCodeBuilderจากไลบรารีApache Commons Lang ตัวอย่าง:
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ส่งกลับค่า false ถ้าตัวถูกดำเนินการแรกเป็นโมฆะ (Effective Java อีกครั้ง)
มีปัญหาบางอย่างที่ควรสังเกตหากคุณกำลังจัดการกับคลาสที่ยังคงใช้ Object-Relationship Mapper (ORM) เช่น Hibernate ถ้าคุณไม่คิดว่ามันซับซ้อนอย่างไม่มีเหตุผลแล้ว!
วัตถุที่โหลดขี้เกียจเป็น subclasses
หากวัตถุของคุณยังคงใช้ 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เป็นเพียงสิ่งเดียวที่จะทำงานได้อย่างถูกต้อง
วัตถุที่โหลดขี้เกียจมีช่องว่าง
ORMs มักใช้ getters เพื่อบังคับให้โหลดวัตถุที่โหลดแบบ lazy ซึ่งหมายความว่าperson.nameจะเป็นnullถ้าpersonมีการโหลดที่ขี้เกียจแม้ว่าperson.getName()บังคับให้โหลดและส่งคืน "John Doe" จากประสบการณ์ของผมพืชผลนี้บ่อยขึ้นhashCode()และequals()มากขึ้น
หากคุณกำลังจัดการกับการออมให้แน่ใจว่าจะมักจะใช้ getters และไม่เคยอ้างอิงในสนามและhashCode()equals()
การบันทึกวัตถุจะเปลี่ยนสถานะของมัน
วัตถุถาวรมักจะใช้idเขตข้อมูลเพื่อเก็บกุญแจของวัตถุ ฟิลด์นี้จะถูกอัปเดตโดยอัตโนมัติเมื่อมีการบันทึกวัตถุเป็นครั้งแรก hashCode()อย่าใช้ข้อมูลประจำตัวประชาชนใน แต่คุณสามารถใช้มันequals()ได้
รูปแบบที่ฉันมักจะใช้คือ
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
แต่คุณไม่สามารถรวมในgetId() hashCode()ถ้าคุณทำเมื่อวัตถุยังคงอยู่การhashCodeเปลี่ยนแปลงของวัตถุ หากวัตถุอยู่ใน a HashSetคุณจะ "ไม่เคย" ค้นหามันอีก
ในของฉันPersonตัวอย่างเช่นผมอาจจะใช้getName()สำหรับการhashCodeและgetId()บวก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ระบุไว้ว่าตัวเลือกพิเศษทั้งหมดมีค่าเริ่มต้นและเช่นเดียวกันสำหรับ a ThingWithOptionSetBดังนั้นมันควรจะเป็นไปได้ThingWithOptionSetAที่จะเปรียบเทียบเท่ากับ a ThingWithOptionSetBเท่านั้นหากคุณสมบัติที่ไม่ใช่ฐานทั้งหมดของวัตถุทั้งสองตรงกับค่าเริ่มต้น แต่ ฉันไม่เห็นวิธีการทดสอบของคุณ
B b2 = new B(1,99)แล้วb.equals(a) == trueและแต่a.equals(b2) == true b.equals(b2) == false
สำหรับการติดตั้งที่ง่ายต่อการสืบทอดให้ตรวจสอบโซลูชันของ Tal Cohen ฉันจะใช้วิธีการเท่ากับ () ได้อย่างถูกต้องได้อย่างไร
สรุป:
ในหนังสือคู่มือภาษาจาวาที่มีประสิทธิภาพ (แอดดิสัน - เวสลีย์, 2544) โจชัวโบลชอ้างว่า "ไม่มีทางที่จะขยายชั้นเรียนได้ทันทีและเพิ่มมุมมองใหม่ ๆ ทัลไม่เห็นด้วย
วิธีแก้ปัญหาของเขาคือการใช้ equals () โดยการเรียกอีกอย่างที่ไม่สมมาตร blindlyEquals () ทั้งสองวิธี blindlyEquals () ถูกแทนที่โดยคลาสย่อยเท่ากับ () จะถูกสืบทอดและไม่เคยถูกแทนที่
ตัวอย่าง:
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นั้นเป็นที่น่าพอใจ
if (this.getClass() != o.getClass()) return 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 อย่างไรก็ตาม
มีสองวิธีในคลาส super เป็น java.lang.Object เราจำเป็นต้องแทนที่พวกเขาไปยังวัตถุที่กำหนดเอง
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())ใช้ฉันใช้ # 1 ในการfinalใช้งานที่เท่าเทียมกันหรือเมื่อใช้งานอินเทอร์เฟซที่กำหนดอัลกอริทึมสำหรับเท่ากับ (เช่นjava.utilอินเทอร์เฟซการรวบรวม - วิธีที่เหมาะสมในการตรวจสอบด้วย(obj instanceof Set)อินเทอร์เฟซที่เหมาะสม โดยทั่วไปแล้วมันเป็นตัวเลือกที่ไม่ดีเมื่อสามารถลบล้างเพราะจะทำให้คุณสมบัติสมมาตรแตก
ตัวเลือก # 2 ช่วยให้ชั้นเรียนสามารถขยายออกไปได้อย่างปลอดภัยโดยไม่ต้องลบล้างเท่ากับหรือทำลายความสมมาตร
หากชั้นเรียนของคุณเป็นเช่นComparableนั้นวิธีการequalsและcompareToควรสอดคล้องกันด้วย นี่คือเทมเพลตสำหรับวิธีการที่เท่าเทียมกันในComparableคลาส:
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 แลงเกอร์ ฉันรักมันมาก เธอยังเป็นคำถามที่พบบ่อยที่ดีเกี่ยวกับยาสามัญในชวา ดูบทความอื่น ๆ ของเธอที่นี่ (เลื่อนลงไปที่ "คอร์ Java") ซึ่งเธอยังไปกับ Part-2 และ "การเปรียบเทียบประเภทผสม" ขอให้สนุกกับการอ่านมัน!
equals () วิธีการที่ใช้ในการกำหนดความเท่าเทียมกันของวัตถุทั้งสอง
ค่า int ของ 10 จะเท่ากับ 10 เสมอ แต่เมธอด equals () นี้เกี่ยวกับความเท่าเทียมกันของวัตถุทั้งสอง เมื่อเราพูดวัตถุมันจะมีคุณสมบัติ ในการตัดสินใจเกี่ยวกับความเสมอภาคคุณสมบัติเหล่านั้นจะถูกพิจารณา ไม่จำเป็นว่าจะต้องคำนึงถึงคุณสมบัติทั้งหมดเพื่อพิจารณาความเสมอภาคและด้วยความเคารพต่อคำจำกัดความของชั้นเรียนและบริบทที่สามารถตัดสินใจได้ จากนั้นวิธี equals () สามารถแทนที่ได้
เราควรแทนที่เมธอด hashCode () ทุกครั้งที่เราแทนที่เมธอด equals () ถ้าไม่จะเกิดอะไรขึ้น หากเราใช้แฮชเทเบิลในแอปพลิเคชันของเรามันจะไม่ทำงานตามที่คาดไว้ เนื่องจาก hashCode ถูกใช้ในการพิจารณาความเท่าเทียมกันของค่าที่เก็บไว้มันจะไม่ส่งคืนค่าที่สอดคล้องกันที่เหมาะสมสำหรับคีย์
การใช้งานเริ่มต้นที่กำหนดคือ 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
เรามีเหตุผล:
a.getClass().equals(b.getClass()) && a.equals(b) ⇒ a.hashCode() == b.hashCode()
แต่ไม่ใช่ในทางกลับกัน!
หนึ่ง gotcha ที่ฉันได้พบคือที่ซึ่งวัตถุสองชิ้นมีการอ้างอิงถึงกันและกัน (ตัวอย่างหนึ่งคือความสัมพันธ์ของผู้ปกครอง / เด็กที่มีวิธีการอำนวยความสะดวกในผู้ปกครองเพื่อรับลูกทั้งหมด)
สิ่งต่าง ๆ เหล่านี้เป็นเรื่องธรรมดาเมื่อทำการจับคู่ไฮเบอร์เนต
หากคุณรวมทั้งสองด้านของความสัมพันธ์ใน hashCode หรือการทดสอบเท่ากับคุณสามารถเข้าสู่วนซ้ำแบบซ้ำซึ่งจบลงใน StackOverflowException
วิธีที่ง่ายที่สุดคือไม่รวมคอลเลกชัน getChildren ไว้ในเมธอด
equals()ไม่ควรมีส่วนร่วมใน หากนักวิทยาศาสตร์บ้าสร้างซ้ำของฉันเราจะเทียบเท่า แต่เราจะไม่มีพ่อคนเดียวกัน