2010-01-14 38 views
53

Có một quy tắc cụ thể về cách Overriding equals() & hashCode() trong lớp tiểu xem xét siêu lĩnh vực ?? biết rằng có nhiều thông số: siêu trường là private/public, có/không có getter ...bình đẳng Overriding() & hashCode() trong các lớp học phụ ... xem xét siêu lĩnh vực

Ví dụ, Netbeans tạo equals() & hashCode() sẽ không xem xét các lĩnh vực siêu ... và

new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
    new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot")) 

sẽ trở thành sự thật :(

public class Hominidae { 

    public String gender; 
    public String weight; 
    public String height; 

    public Hominidae(String gender, String weight, String height) { 
     this.gender = gender; 
     this.weight = weight; 
     this.height = height; 
    } 
    ... 
} 

public class HomoSapiens extends Hominidae { 
    public String name; 
    public String faceBookNickname; 

    public HomoSapiens(String gender, String weight, String height, 
         String name, String facebookId) { 
     super(gender, weight, height); 
     this.name = name; 
     this.faceBookNickname = facebookId; 
    } 
    ... 
} 

Nếu bạn muốn xem Netbeans tạo equals() & hashCode():

public class Hominidae { 

    ... 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     final Hominidae other = (Hominidae) obj; 
     if ((this.gender == null) ? (other.gender != null) : !this.gender.equals(other.gender)) { 
      return false; 
     } 
     if ((this.weight == null) ? (other.weight != null) : !this.weight.equals(other.weight)) { 
      return false; 
     } 
     if ((this.height == null) ? (other.height != null) : !this.height.equals(other.height)) { 
      return false; 
     } 
     return true; 
    } 

    @Override 
    public int hashCode() { 
     int hash = 5; 
     hash = 37 * hash + (this.gender != null ? this.gender.hashCode() : 0); 
     hash = 37 * hash + (this.weight != null ? this.weight.hashCode() : 0); 
     hash = 37 * hash + (this.height != null ? this.height.hashCode() : 0); 
     return hash; 
    } 

} 


public class HomoSapiens extends Hominidae { 

    ... 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     final HomoSapiens other = (HomoSapiens) obj; 
     if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { 
      return false; 
     } 
     if ((this.faceBookNickname == null) ? (other.faceBookNickname != null) : !this.faceBookNickname.equals(other.faceBookNickname)) { 
      return false; 
     } 
     return true; 
    } 

    @Override 
    public int hashCode() { 
     int hash = 7; 
     hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0); 
     hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0); 
     return hash; 
    } 
} 

Trả lời

55

Trẻ em không nên kiểm tra các thành viên private của cha mẹ

Nhưng rõ ràng, tất cả các lĩnh vực quan trọng nên được đưa vào tính toán cho sự bình đẳng và băm.

May mắn thay, bạn có thể dễ dàng đáp ứng cả hai quy tắc.

Giả sử bạn không bị mắc kẹt khi sử dụng NetBeans-equals và hashcode, bạn có thể sửa đổi phương thức equals của Hominidae để sử dụng so sánh instanceof chứ không phải đẳng thức, và sau đó sử dụng nó đơn giản. Một cái gì đó như thế này:


    @Override 
    public boolean equals(Object obj) { 
     if (obj == null) { return false; } 
     if (getClass() != obj.getClass()) { return false; } 
     if (! super.equals(obj)) return false; 
     else { 
      // compare subclass fields 
     } 

Tất nhiên, hashcode là dễ dàng:


    @Override  
    public int hashCode() {  
     int hash = super.hashCode(); 
     hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);  
     hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);  
     return hash;  
    }  

Nghiêm túc, mặc dù: có chuyện gì thế với NetBeans không dùng lĩnh vực lớp cha vào tài khoản bằng cách gọi phương pháp lớp cha?

+0

@CPerkins: thật khó để tự động hóa thế hệ này, hãy nghĩ về trường hợp của các trường riêng có/không có getter ... Tôi nghĩ đó là lý do tại sao IDE không làm ... –

+0

@wj, tất cả những gì bạn phải làm là gọi superclass bằng và phương thức hashcode, và các trường được tính đến. IDE có thể dễ dàng làm điều này. Trẻ em không cần phải kiểm tra một cách rõ ràng các thành viên riêng của cha mẹ. – CPerkins

+0

@CPerkins, bạn nói đúng !! –

4

Các quy tắc là:

  • Đó là phản xạ: đối với bất kỳ không null giá trị tham khảo x, x.equals (x) nên trở thành sự thật.
  • Đối xứng là: đối với bất kỳ giá trị tham chiếu không null nào x và y, x.equals (y) sẽ trả về true nếu và chỉ khi y.equals (x) trả về giá trị true.
  • Chuyển tiếp: cho bất kỳ giá trị tham chiếu không null nào x, y và z, nếu x.equals (y) trả về true và y.equals (z) trả về true, thì x.equals (z) phải trả về true .
  • Điều này nhất quán: đối với bất kỳ giá trị tham chiếu không rỗng x và y, nhiều lần gọi x.equals (y) luôn trả về true hoặc return false false, miễn là không có thông tin nào được sử dụng bằng so sánh trên các đối tượng được sửa đổi.
  • Đối với bất kỳ giá trị tham chiếu không null nào x, x.equals (null) sẽ trả về false.
  • là thường cần thiết để ghi đè lên phương thức hashCode bất cứ khi nào phương pháp này được ghi đè, để duy trì hợp đồng chung cho các phương thức hashCode, trong đó nêu rằng đối tượng bằng nhau phải có mã băm bằng

từ Object.equals().

Vì vậy, hãy sử dụng các trường cần thiết để hoàn thành các quy tắc.

+0

Luôn luôn xem xét đặc điểm kỹ thuật thay vì (hoặc bổ sung) việc triển khai và trải nghiệm. –

+0

Lưu ý rằng, hai đối tượng mang lại cùng một 'hashCode' không nhất thiết phải' bằng 'với nhau. –

20

Tôi thích sử dụng EqualsBuilder (và HashcodeBuilder) từ commons-lang package để làm cho phương thức equals() và hashcode() dễ đọc hơn nhiều.

Ví dụ:

public boolean equals(Object obj) { 
if (obj == null) { return false; } 
if (obj == this) { return true; } 
if (obj.getClass() != getClass()) { 
    return false; 
} 
MyClass rhs = (MyClass) obj; 
return new EqualsBuilder() 
      .appendSuper(super.equals(obj)) 
      .append(field1, rhs.field1) 
      .append(field2, rhs.field2) 
      .append(field3, rhs.field3) 
      .isEquals(); 
} 
+1

kudo để đề cập đến '.appendSuper()'. –

2

Bởi vì phá vỡ thừa kế đóng gói, phân lớp mà thực hiện equals() và hashCode() phải, nhất thiết, chiếm đặc thù của superclasses của họ. Tôi đã thành công khi mã hóa các cuộc gọi đến các phương thức equals() và hashCode() của lớp cha từ các phương thức của lớp con.

+0

Tương tự, việc ghi đè 'equals()' và 'hashCode()' trong 'Hominidae' sẽ làm cho các phương thức tương ứng trong' HomoSapiens' tương đối dễ thực hiện hơn. – trashgod

+0

@trashgod: ghi đè bằng() và hashCode() trong Hominidae sẽ không làm cho các phương thức tương ứng trong HomoSapiens dễ thực hiện hơn bởi vì chúng ta không thể làm điều gì đó như "super.equals (obj.super)" trong đó obj là đối tượng được so sánh .. –

+0

@wj: Bạn nói đúng. Tôi đã suy nghĩ về ví dụ này, trong đó gọi các phương thức tương ứng của 'Chuỗi': http://stackoverflow.com/questions/1924728/why-isnt-collections-binarysearch-working-with-this-comparable/1926111#1926111 – trashgod

8

Nói chung việc thực hiện bằng ngang qua các lớp con là khó để giữ đối xứng và chuyển tiếp.

Hãy xem xét một lớp cha để kiểm tra cho lĩnh vực xy, và kiểm tra lớp con cho x, yz.

Vì vậy, một phân lớp == Superclass == Phân lớp trong đó z là khác nhau giữa trường hợp đầu tiên của lớp con và thứ hai, vi phạm phần chuyển tiếp của hợp đồng.

Lý do tại sao việc triển khai điển hình bằng bằng sẽ kiểm tra getClass() != obj.getClass() thay vì thực hiện một instanceof. Trong ví dụ trên, nếu SubClass hoặc Superclass thực hiện instanceof kiểm tra nó sẽ phá vỡ tính đối xứng.

Vì vậy, kết quả là lớp con chắc chắn có thể xem xét super.equals() nhưng cũng nên thực hiện kiểm tra getClass() của riêng nó để tránh các vấn đề trên và sau đó kiểm tra các giá trị bằng trên các trường của chính nó. Nó sẽ là một con vịt kỳ lạ của một lớp học mà thay đổi hành vi tương đương của nó dựa trên các lĩnh vực cụ thể của các siêu lớp hơn là chỉ khi các lớp học trở về bằng.

+0

thx Yishai, nhưng vấn đề ở đây là so sánh 2 trường hợp của phân lớp và vấn đề là chúng ta không thể làm điều gì đó như "super.equals (obj .super) "nơi obj là đối tượng được so sánh –

+0

@wj, Miễn là lớp của bạn và lớp obj là như nhau, tôi không hiểu tại sao bạn không thể gọi' if (! super.equals (obj)) return false ' – Yishai

+0

@Yishai, điểm ở đây là tìm cách so sánh "this.super" và "obj.super" không phải "this.super" và "obj" bởi vì cá thể không trực tiếp của chúng cùng một lớp nghĩa là "siêu .equals (obj) "luôn luôn là sai ... trong ví dụ của tôi" this.super "là một" Hominidae "khi" obj "là một" HomoSapiens " –

1

Có vẻ như lớp cha mẹ (siêu) của bạn không ghi đè bằng. Nếu đây là trường hợp thì bạn cần so sánh các trường từ lớp cha khi bạn ghi đè phương thức này trong lớp con. Tôi đồng ý rằng bằng cách sử dụng commons EqualsBuiler là con đường để đi nhưng bạn cần phải cẩn thận rằng bạn không phá vỡ các phần đối xứng/transative của hợp đồng bằng.

Nếu lớp con của bạn thêm thuộc tính vào lớp cha và lớp cha không phải là tóm tắt và ghi đè bằng bạn sẽ gặp rắc rối. Trong trường hợp này, bạn thực sự nên xem xét thành phần đối tượng thay vì thừa kế.

Tôi đặc biệt khuyên bạn nên sử dụng phần này trong Java hiệu quả của Joshua Block. Đó là toàn diện và thực sự giải thích tốt.

2

Về câu trả lời @CPerkins được chấp nhận, tôi không nghĩ rằng mã equals() nhất định sẽ hoạt động đáng tin cậy, do khả năng phương thức super.equals() cũng sẽ kiểm tra sự bình đẳng của lớp. Lớp con của lớp con & sẽ không có các lớp bằng nhau.

+0

Đây không phải là câu trả lời, mà là một bình luận. Đó là một bình luận rất hợp lệ mặc dù. –

+0

Siêu.equals() và super.hashcode() của lớp cha cần được viết theo cách mà chúng làm việc cho các lớp con và nếu không chúng phải là cuối cùng. –

+0

bit trễ của một phản ứng, nhưng vẫn: điều này đặt ra không có vấn đề gì cả. Sau khi tất cả: thời điểm bạn gọi super.equals, bạn vẫn gọi nó từ một thể hiện của lớp con. Lớp siêu có chứa một (this.) GetClass() không thay đổi thực tế là phương thức được gọi trên cá thể, và lớp của cá thể đó không thay đổi. – Stultuske

1

Vâng, HomoSapiens#hashcode sẽ là đủ với câu trả lời của CPerkins.

@Override  
public int hashCode() {  
    int hash = super.hashCode(); 
    hash = 89 * hash + Objects.hash(name);  
    hash = 89 * hash + Objects.hash(faceBookNickname);  
    return hash;  
} 

Nếu bạn muốn các lĩnh vực những của cha mẹ (gender, weight, height) trong hành động, một trong những cách được tạo ra một thể hiện thực tế của loại phụ huynh và sử dụng nó. Cảm ơn Chúa, nó không phải là một lớp trừu tượng.

@Override 
public boolean equals(Object obj) { 
    if (obj == null) { 
     return false; 
    } 
    if (getClass() != obj.getClass()) { 
     return false; 
    } 
    final HomoSapiens other = (HomoSapiens) obj; 
    if (!super.equals(new Hominidae(
     other.gender, other.weight, other.height))) { 
     return false; 
    } 
    if (!Objects.equals(name, other.name)) return false; 
    if (!Objects.equals(faceBookNickname, other.faceBookNickname)) 
     return false; 
    return true; 
} 

Tôi đang thêm một cách để (tôi nghĩ) giải quyết vấn đề này. Điểm mấu chốt là thêm một phương pháp kiểm tra một cách lỏng lẻo sự bình đẳng.

public class Parent { 

    public Parent(final String name) { 
     super(); this.name = name; 
    } 

    @Override 
    public int hashCode() { 
     return hash = 53 * 7 + Objects.hashCode(name); 
    } 

    @Override 
    public boolean equals(final Object obj) { 
     return equalsAs(obj) && getClass() == obj.getClass(); 
    } 

    protected boolean equalsAs(final Object obj) { 
     if (this == obj) return true; 
     if (obj == null) return false; 
     if (!getClass().isAssignableFrom(obj.getClass())) return false; 
     final Parent other = (Parent) obj; 
     if (!Objects.equals(name, other.name)) return false; 
     return true; 
    } 

    private final String name; 
} 

Và ở đây có Child.

public class Child extends Parent { 

    public Child(final String name, final int age) { 
     super(name); this.age = age; 
    } 

    @Override 
    public int hashCode() { 
     return hash = 31 * super.hashCode() + age; 
    } 

    @Override 
    public boolean equals(final Object obj) { 
     return super.equals(obj); 
    } 

    @Override 
    protected boolean equalsAs(final Object obj) { 
     if (!super.equalsAs(obj)) return false; 
     if (!getClass().isAssignableFrom(obj.getClass())) return false; 
     final Child other = (Child) obj; 
     if (age != other.age) return false; 
     return true; 
    } 

    private final int age; 
} 

Testing ...

@Test(invocationCount = 128) 
public void assertReflective() { 
    final String name = current().nextBoolean() ? "null" : null; 
    final int age = current().nextInt(); 
    final Child x = new Child(name, age); 
    assertTrue(x.equals(x)); 
    assertEquals(x.hashCode(), x.hashCode()); 
} 

@Test(invocationCount = 128) 
public void assertSymmetric() { 
    final String name = current().nextBoolean() ? "null" : null; 
    final int age = current().nextInt(); 
    final Child x = new Child(name, age); 
    final Child y = new Child(name, age); 
    assertTrue(x.equals(y)); 
    assertEquals(x.hashCode(), y.hashCode()); 
    assertTrue(y.equals(x)); 
    assertEquals(y.hashCode(), x.hashCode()); 
} 

@Test(invocationCount = 128) 
public void assertTransitive() { 
    final String name = current().nextBoolean() ? "null" : null; 
    final int age = current().nextInt(); 
    final Child x = new Child(name, age); 
    final Child y = new Child(name, age); 
    final Child z = new Child(name, age); 
    assertTrue(x.equals(y)); 
    assertEquals(x.hashCode(), y.hashCode()); 
    assertTrue(y.equals(z)); 
    assertEquals(y.hashCode(), z.hashCode()); 
    assertTrue(x.equals(z)); 
    assertEquals(x.hashCode(), z.hashCode()); 
} 
0

Nó đáng chú ý là thế hệ tự động IDE có lẽ đã đưa vào xem xét siêu lớp, chỉ với điều kiện equals() và hashCode() của siêu lớp tồn tại được nêu ra . Đó là, nên tự động tạo ra hai chức năng của siêu đầu tiên, và sau đó tự động tạo ra các con. Tôi có ví dụ bên dưới bên dưới theo ý tưởng Intellj:

@Override 
public boolean equals(Object o) { 
    if (this == o) return true; 
    if (o == null || getClass() != o.getClass()) return false; 
    if (!super.equals(o)) return false; 

    TActivityWrapper that = (TActivityWrapper) o; 

    return data != null ? data.equals(that.data) : that.data == null; 
} 

@Override 
public int hashCode() { 
    int result = super.hashCode(); 
    result = 31 * result + (data != null ? data.hashCode() : 0); 
    return result; 
} 

Vấn đề xảy ra khi bạn không tự động tạo siêu dữ liệu trước. Vui lòng kiểm tra ở trên dưới Netbeans.

+1

Không. Nhìn vào dòng thứ hai của equals của bạn() thực hiện. Nếu superclass có cùng một dòng (như nó cần), thì các cuộc gọi tới super.equals (o) sẽ thất bại vì chúng không cùng loại. Lớp con thực sự cần so sánh TẤT CẢ các thuộc tính xác định sự bình đẳng, không chỉ các thuộc tính được khai báo cục bộ. Các trường riêng không thể truy cập ngay cả thông qua getter thực sự không nên là một phần của kiểm tra bình đẳng trong lớp con, ngữ nghĩa. Tại sao quan tâm đến một sự khác biệt mà không thể được nhìn thấy? – ideasculptor

Các vấn đề liên quan