จะต้องพิจารณาประเด็น / ข้อผิดพลาดใดบ้างเมื่อเอาชนะ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()
ไม่ควรมีส่วนร่วมใน หากนักวิทยาศาสตร์บ้าสร้างซ้ำของฉันเราจะเทียบเท่า แต่เราจะไม่มีพ่อคนเดียวกัน