Những vấn đề / cạm bẫy phải được xem xét khi ghi đè equals
và hashCode
?
Những vấn đề / cạm bẫy phải được xem xét khi ghi đè equals
và hashCode
?
Câu trả lời:
equals()
( javadoc ) phải xác định mối quan hệ tương đương (nó phải là phản xạ , đối xứng và bắc cầu ). Ngoài ra, nó phải nhất quán (nếu các đối tượng không được sửa đổi, thì nó phải tiếp tục trả về cùng một giá trị). Hơn nữa, o.equals(null)
phải luôn luôn trả lại sai.
hashCode()
( javadoc ) cũng phải nhất quán (nếu đối tượng không được sửa đổi về mặt equals()
, nó phải tiếp tục trả về cùng một giá trị).
Các mối quan hệ giữa hai phương pháp là:
Bất cứ khi nào
a.equals(b)
, sau đóa.hashCode()
phải giống nhưb.hashCode()
.
Nếu bạn ghi đè lên một, thì bạn nên ghi đè lên cái khác.
Sử dụng cùng một bộ các trường mà bạn sử dụng để tính toán equals()
để tính toánhashCode()
.
Sử dụng các lớp trình trợ giúp tuyệt vời EqualsBuilder và HashCodeBuilder từ thư viện Apache Commons Lang . Một ví dụ:
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();
}
}
Khi sử dụng Bộ sưu tập hoặc Bản đồ dựa trên hàm băm , chẳng hạn như Hashset , LinkedHashset , HashMap , Hashtable hoặc WeakHashMap , hãy đảm bảo rằng hashCode () của các đối tượng chính mà bạn đưa vào bộ sưu tập không bao giờ thay đổi trong khi đối tượng nằm trong bộ sưu tập. Cách chống đạn để đảm bảo điều này là làm cho chìa khóa của bạn trở nên bất biến, cũng có những lợi ích khác .
instanceof
trả về false nếu toán hạng đầu tiên của nó là null (Java lại hiệu quả).
Có một số vấn đề đáng chú ý nếu bạn đang xử lý các lớp vẫn tồn tại khi sử dụng Trình ánh xạ mối quan hệ đối tượng (ORM) như Hibernate, nếu bạn không nghĩ rằng điều này đã phức tạp một cách vô lý!
Các đối tượng tải lười biếng là các lớp con
Nếu các đối tượng của bạn được duy trì bằng ORM, trong nhiều trường hợp, bạn sẽ xử lý các proxy động để tránh tải đối tượng quá sớm từ kho lưu trữ dữ liệu. Các proxy này được triển khai như các lớp con của lớp của riêng bạn. Điều này có nghĩa là this.getClass() == o.getClass()
sẽ trở lại false
. Ví dụ:
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
Nếu bạn đang xử lý ORM, sử dụng o instanceof Person
là điều duy nhất sẽ hành xử chính xác.
Các đối tượng tải lười biếng có các trường rỗng
Các ORM thường sử dụng các getters để buộc tải các đối tượng tải lười biếng. Điều này có nghĩa rằng person.name
sẽ null
nếu person
là lười biếng nạp, ngay cả khi person.getName()
lực lượng bốc và lợi nhuận "John Doe". Theo kinh nghiệm của tôi, cây trồng này thường xuyên hơn trong hashCode()
vàequals()
.
Nếu bạn đang xử lý ORM, hãy đảm bảo luôn sử dụng getters và không bao giờ tham chiếu trường trong hashCode()
và equals()
.
Lưu một đối tượng sẽ thay đổi trạng thái của nó
Các đối tượng liên tục thường sử dụng một id
trường để giữ khóa của đối tượng. Trường này sẽ được cập nhật tự động khi một đối tượng được lưu lần đầu tiên. Đừng sử dụng một trường id trong hashCode()
. Nhưng bạn có thể sử dụng nó trong equals()
.
Một mẫu tôi thường sử dụng là
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Nhưng: bạn không thể bao gồm getId()
trong hashCode()
. Nếu bạn làm, khi một đối tượng được duy trì, nó hashCode
sẽ thay đổi. Nếu đối tượng ở trong một HashSet
, bạn sẽ "không bao giờ" tìm thấy nó nữa.
Trong Person
ví dụ của tôi , tôi có thể sẽ sử dụng getName()
cho hashCode
và getId()
cộng getName()
(chỉ cho hoang tưởng) cho equals()
. Không sao nếu có một số rủi ro "va chạm" hashCode()
, nhưng không bao giờ ổn equals()
.
hashCode()
nên sử dụng tập hợp con không thay đổi của các thuộc tính từ equals()
Saving an object will change it's state
! hashCode
phải trả lại int
, vậy bạn sẽ sử dụng getName()
như thế nào? Bạn có thể đưa ra một ví dụ chohashCode
Một sự làm rõ về obj.getClass() != getClass()
.
Tuyên bố này là kết quả của equals()
việc thừa kế không thân thiện. JLS (đặc tả ngôn ngữ Java) chỉ định rằng nếu A.equals(B) == true
sau đó B.equals(A)
cũng phải trả về true
. Nếu bạn bỏ qua câu lệnh đó kế thừa các lớp ghi đè equals()
(và thay đổi hành vi của nó) sẽ phá vỡ đặc tả này.
Xem xét ví dụ sau về những gì xảy ra khi câu lệnh bị bỏ qua:
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));
}
}
Làm thêm new A(1).equals(new A(1))
, new B(1,1).equals(new B(1,1))
kết quả đưa ra đúng, như nó nên.
Điều này có vẻ rất tốt, nhưng hãy xem điều gì sẽ xảy ra nếu chúng ta cố gắng sử dụng cả hai lớp:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Rõ ràng, điều này là sai.
Nếu bạn muốn đảm bảo điều kiện đối xứng. a = b nếu b = a và nguyên tắc thay thế Liskov gọi super.equals(other)
không chỉ trong trường hợp B
ví dụ, mà còn kiểm tra sau 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;
Sẽ xuất ra:
a.equals(b) == true;
b.equals(a) == true;
Trường hợp, nếu a
không phải là một tham chiếu của B
, thì nó có thể là một tham chiếu của lớp A
(vì bạn mở rộng nó), trong trường hợp này bạn super.equals()
cũng gọi .
ThingWithOptionSetA
có thể bằng với một Thing
điều kiện là tất cả các tùy chọn bổ sung đều có giá trị mặc định và tương tự như vậy đối với a ThingWithOptionSetB
, thì có ThingWithOptionSetA
thể so sánh bằng một ThingWithOptionSetB
chỉ khi tất cả các thuộc tính không cơ sở của cả hai đối tượng khớp với mặc định của chúng, nhưng Tôi không thấy cách bạn kiểm tra cho điều đó.
B b2 = new B(1,99)
, sau đó b.equals(a) == true
và a.equals(b2) == true
nhưng b.equals(b2) == false
.
Để triển khai thân thiện với thừa kế, hãy xem giải pháp của Tal Cohen, Làm thế nào để tôi thực hiện đúng phương thức bằng ()?
Tóm lược:
Trong cuốn sách Hướng dẫn ngôn ngữ lập trình Java hiệu quả (Addison-Wesley, 2001), Joshua Bloch tuyên bố rằng "Đơn giản là không có cách nào để mở rộng một lớp khả thi và thêm một khía cạnh trong khi duy trì hợp đồng bằng nhau." Tal không đồng ý.
Giải pháp của anh ấy là thực hiện bằng () bằng cách gọi một cách khác là BlindlyEquals () cả hai cách. blindlyEquals () bị ghi đè bởi các lớp con, bằng () được kế thừa và không bao giờ bị ghi đè.
Thí dụ:
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);
}
}
Lưu ý rằng bằng () phải hoạt động trên các hệ thống phân cấp thừa kế nếu Nguyên tắc thay thế Liskov được thỏa mãn.
if (this.getClass() != o.getClass()) return false
, nhưng linh hoạt ở chỗ nó chỉ trả về false nếu (các) lớp dẫn xuất bận tâm sửa đổi bằng. Có đúng không?
Vẫn ngạc nhiên rằng không ai đề nghị thư viện ổi cho việc này.
//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
trong this.getDate()
phương tiện gì (trừ lộn xộn)
if (!(otherObject instanceof DateAndPattern)) {
. Đồng ý với hernan và Steve Kuo (mặc dù đó là vấn đề sở thích cá nhân), nhưng dù sao +1.
Có hai phương thức trong siêu lớp là java.lang.Object. Chúng ta cần ghi đè chúng vào đối tượng tùy chỉnh.
public boolean equals(Object obj)
public int hashCode()
Các đối tượng bằng nhau phải tạo ra cùng một mã băm miễn là chúng bằng nhau, tuy nhiên các đối tượng không bằng nhau không cần tạo ra các mã băm riêng biệt.
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
}
Nếu bạn muốn nhận thêm, vui lòng kiểm tra liên kết này dưới dạng http://www.javaranch.com/journal/2002/10/equalhash.html
Đây là một ví dụ khác, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
Chúc vui vẻ! @. @
Có một số cách để kiểm tra sự bình đẳng của lớp trước khi kiểm tra sự bình đẳng của thành viên và tôi nghĩ cả hai đều hữu ích trong trường hợp phù hợp.
instanceof
toán tử.this.getClass().equals(that.getClass())
.Tôi sử dụng số 1 trong final
triển khai bằng, hoặc khi triển khai giao diện quy định thuật toán cho bằng (như java.util
giao diện bộ sưu tập Cách thức đúng để kiểm tra(obj instanceof Set)
hoặc bất kỳ giao diện nào bạn đang triển khai). Nói chung, đó là một lựa chọn tồi khi các giá trị bằng có thể bị ghi đè vì phá vỡ thuộc tính đối xứng.
Tùy chọn # 2 cho phép lớp được mở rộng một cách an toàn mà không bị ghi đè bằng hoặc phá vỡ tính đối xứng.
Nếu lớp của bạn cũng vậy Comparable
, equals
và compareTo
các phương thức cũng phải nhất quán. Đây là một mẫu cho phương thức bằng trong một Comparable
lớp:
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
, và compareTo()
phương thức được ghi đè để đảo ngược thứ tự sắp xếp, các thể hiện của lớp con và siêu lớp không nên được coi là bằng nhau. Khi các đối tượng này được sử dụng cùng nhau trong một cây, các khóa "bằng" theo cách instanceof
triển khai có thể không tìm thấy được.
Đối với bằng, hãy nhìn vào Secrets of Equals của Angelika Langer . Tôi rất yêu nó. Cô ấy cũng là một Câu hỏi thường gặp tuyệt vời về Generics trong Java . Xem các bài viết khác của cô ấy tại đây (cuộn xuống "Core Java"), nơi cô ấy cũng tiếp tục với Phần 2 và "so sánh kiểu hỗn hợp". Hãy vui vẻ đọc chúng!
Phương thức equals () được sử dụng để xác định đẳng thức của hai đối tượng.
as int value của 10 luôn bằng 10. Nhưng phương thức equals () này là về đẳng thức của hai đối tượng. Khi chúng ta nói đối tượng, nó sẽ có các thuộc tính. Để quyết định về bình đẳng những tài sản được xem xét. Không nhất thiết là tất cả các thuộc tính phải được tính đến để xác định sự bằng nhau và liên quan đến định nghĩa lớp và bối cảnh có thể được quyết định. Sau đó, phương thức equals () có thể được ghi đè.
chúng ta nên luôn ghi đè phương thức hashCode () bất cứ khi nào chúng ta ghi đè phương thức equals (). Nếu không, chuyện gì sẽ xảy ra? Nếu chúng tôi sử dụng hashtables trong ứng dụng của mình, nó sẽ không hoạt động như mong đợi. Vì hashCode được sử dụng để xác định sự bằng nhau của các giá trị được lưu trữ, nó sẽ không trả về giá trị tương ứng cho một khóa.
Việc thực hiện mặc định được đưa ra là phương thức hashCode () trong lớp Object sử dụng địa chỉ bên trong của đối tượng và chuyển đổi nó thành số nguyên và trả về nó.
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;
}
}
Mã đầu ra ví dụ:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Một gotcha tôi đã tìm thấy là nơi hai đối tượng chứa các tham chiếu cho nhau (một ví dụ là mối quan hệ cha mẹ / con cái với một phương thức tiện lợi trên cha mẹ để có được tất cả con cái).
Những thứ này khá phổ biến khi thực hiện ánh xạ Hibernate chẳng hạn.
Nếu bạn bao gồm cả hai đầu của mối quan hệ trong hàm hashCode của mình hoặc bằng với các kiểm tra thì có thể vào vòng lặp đệ quy kết thúc trong StackOverflowException.
Giải pháp đơn giản nhất là không bao gồm bộ sưu tập getChildren trong các phương thức.
equals()
. Nếu một nhà khoa học điên tạo ra một bản sao của tôi, chúng ta sẽ tương đương. Nhưng chúng tôi sẽ không có cùng một người cha.