2011-01-20 67 views
19

Có trường hợp nào có ý nghĩa đối với một lớp học để thực hiện các phương pháp equals()hashCode() của chúng tôi bằng cách sử dụng một nhóm các trường khác nhau không?Java bằng() và hashCode() dựa trên các trường khác nhau?

Tôi hỏi vì tôi đang bối rối bởi máy phát Netbeans equals()hashCode(), nơi bạn được yêu cầu chọn các trường để bao gồm trong từng phương pháp riêng biệt. Tôi luôn luôn chọn cùng một lĩnh vực cho cả hai phương pháp, nhưng có một tình huống mà đây không phải là sự lựa chọn đúng không?

+1

Với hàm ý rằng trình tạo mã Netbeans là sai để đưa ra lựa chọn nếu không bao giờ có lý do chính đáng để chọn các trường khác nhau. – Raedwald

Trả lời

22

Vâng, equals()phải sử dụng tất cả các trường được sử dụng bởi hashCode(), nếu không bạn có thể nhận mã băm khác nhau cho các đối tượng bằng nhau. Tuy nhiên, điều ngược lại không đúng - bạn có thể chọn không tính đến một trường cụ thể khi chọn mã băm. Bằng cách đó bạn có thể kết thúc với cùng một mã băm cho hai đối tượng không bằng nhau mà chỉ khác nhau bởi trường "không sử dụng" (trái ngược với thông qua va chạm tự nhiên). Bạn chỉ muốn điều đó trong một tình huống mà bạn biết va chạm sẽ không xảy ra nhưng bạn sẽ bị bẻ khóa . Tôi tưởng tượng nó cực kỳ hiếm hoi :)

Một trường hợp khác là nơi bạn đã so sánh bình đẳng tùy chỉnh - chẳng hạn như so sánh chuỗi không phân biệt chữ hoa chữ thường - khó khăn hoặc đắt tiền để tạo mã băm cho trường. Một lần nữa, điều này sẽ dẫn đến khả năng xảy ra va chạm nhiều hơn nhưng sẽ hợp lệ.

+1

"bạn có thể chọn không tính đến [một số trường]", ví dụ, nếu những trường đó chỉ giữ giá trị được lưu trữ được tính từ các trường khác. – Raedwald

+0

@Raedwald: Nhưng tại sao bạn lại bao gồm những lĩnh vực bình đẳng trong trường hợp đó? Có lẽ tôi đã hiểu lầm đề nghị của bạn ... nhưng bạn nói đúng rằng các lĩnh vực tiền xử lý có thể có liên quan. –

+0

Bạn sẽ ** loại trừ ** các trường chứa giá trị được lưu trong bộ nhớ cache. – Raedwald

2

Nói chung, bạn nên sử dụng cùng một trường. Từ các tài liệu equals():

Lưu ý rằng nó nói chung là 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ình đẳng .

Từ các tài liệu hashCode():

Nếu hai vật đều bình đẳng theo các bằng phương pháp (Object), sau đó gọi phương thức hashCode trên mỗi của hai đối tượng phải xuất trình kết quả số nguyên cùng.

Lưu ý rằng ngược lại là không đúng sự thật - bạn có thể có hai đối tượng với hashcode cùng mà không bằng nhau (Đây là cách một số cấu trúc dữ liệu giải quyết va chạm)

Vì vậy, về mặt lý thuyết nó có thể sử dụng một tập hợp con của các trường phương thức equals(..) cho phương pháp hashCode(), nhưng tôi không thể nghĩ rằng nếu một lý do thực tế để làm như vậy.

+1

Điều này là sai. Hai đối tượng có mã băm bằng nhau không cần phải bằng nhau, do đó, nó đủ để sử dụng một tập hợp con các trường được sử dụng cho bằng để tính toán mã băm. Xem câu trả lời của Jon Skeet. – sfussenegger

+1

@sfussenegger cảm ơn, đã sửa lại tuyên bố của tôi. – Bozho

+2

lý do thực tế duy nhất tôi có thể nghĩ là lười biếng - thôi, đó là cách gõ phải;) - và hiệu suất, ví dụ: nếu một bộ sưu tập góp phần bình đẳng. Ngoài ra, có thể có một hoặc hai trường gần như là duy nhất. btw, downvote không phải của tôi mặc dù;) – sfussenegger

1

Tôi không nghĩ là có. Tôi blogged about this topic previously - Tôi nghĩ rằng đó là một lỗ hổng giao diện người dùng trong NetBeans mà họ cho phép bạn chọn chúng độc lập với nhau. Từ bài viết trên blog của tôi:

này post from bytes.com làm một công việc tốt trong việc giải thích này:

Trọng phương thức hashCode.

Hợp đồng cho phương thức equals sẽ thực sự có một dòng khác nói rằng bạn phải tiến hành ghi đè phương thức hashCode sau khi ghi đè phương thức equals. Phương thức hashCode được hỗ trợ vì lợi ích của các bộ sưu tập dựa trên băm.

Hợp đồng

Một lần nữa từ số kỹ thuật:

Bất cứ khi nào nó được gọi trên cùng một đối tượng nhiều hơn một lần trong một cuộc thi của một ứng dụng, phương thức hashCode luôn phải trả lại số nguyên cùng, với điều kiện không có thông tin được sử dụng bằng bằng so sánh trên đối tượng được sửa đổi. Số nguyên này không cần phải duy trì sự nhất quán từ một lần thực thi của một ứng dụng đến một thực thi khác của cùng một ứng dụng. Nếu hai đối tượng bằng nhau theo phương thức equals (Object), sau đó gọi phương thức hashCode trên mỗi đối tượng phải tạo ra cùng một kết quả nguyên. Không bắt buộc nếu hai đối tượng không bằng nhau theo phương thức equals, sau đó gọi phương thức hashCode trên mỗi đối tượng phải tạo ra các kết quả nguyên khác biệt. Tuy nhiên, lập trình viên cần lưu ý rằng việc tạo ra các kết quả nguyên phân riêng biệt cho các đối tượng không bằng nhau có thể cải thiện hiệu năng của các bảng băm. Vì vậy, các đối tượng bằng nhau phải có hashCodes bằng nhau. Một cách dễ dàng để đảm bảo rằng điều kiện này luôn được thỏa mãn là sử dụng các thuộc tính giống nhau được sử dụng để xác định sự bình đẳng trong việc xác định hashCode. Bây giờ bạn sẽ thấy lý do tại sao điều quan trọng là ghi đè hashCode mỗi lần bạn ghi đè bằng.

Đó là câu từ đoạn cuối cùng tiền nó lên: “Một cách dễ dàng để đảm bảo rằng tình trạng này luôn luôn được thỏa mãn là sử dụng các thuộc tính cùng sử dụng trong việc xác định sự bình đẳng trong việc xác định hashCode”.

2

Jon Skeet đã làm tốt công việc trả lời câu hỏi này (như anh ấy luôn làm). Tuy nhiên, tôi muốn thêm rằng đây là triển khai hợp lệ cho bất kỳ việc triển khai nào thực hiện bằng

public int hashCode() { 
    return 42; 
} 

Tự nhiên, hiệu suất của cấu trúc dữ liệu băm sẽ giảm đáng kể. Tuy nhiên, tốt hơn là giết hiệu suất hơn là phá vỡ chúng. Vì vậy, nếu bạn đã từng quyết định ghi đè bằng nhưng không thấy bất kỳ nhu cầu nào để cung cấp việc triển khai hashCode sane, thì đó là cách của người lười biếng.

-1

Đọc Java hiệu quả ở chương 3: "Always override hashCode when you override equals".

Và tôi nghĩ nếu đối tượng của bạn sẽ không bao giờ được đưa vào bộ sưu tập dựa trên băm, bạn không cần phải ghi đè hashCode.

1

Theo dõi câu trả lời của Jon Skeet, gần đây tôi đã gặp phải trường hợp tôi cần triển khai phương thức hashCode chỉ với một tập con của các trường được sử dụng trong phương thức equals. Kịch bản (được đơn giản hóa) là:

Tôi có hai lớp AB mỗi trường có chứa tham chiếu đến phần khác ngoài việc có một khóa Chuỗi được xác định. Sử dụng hashCode tự động và bằng máy phát điện trong Eclipse (trong đó, không giống như Netbeans, chỉ cung cấp cho các tùy chọn để sử dụng các lĩnh vực tương tự ở cả hai phương pháp) Tôi kết thúc với các lớp sau:

public class A { 

    public B b; 
    public String bKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((b == null) ? 0 : b.hashCode()); 
     result = prime * result + ((bKey == null) ? 0 : bKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof A)) 
      return false; 
     A other = (A) obj; 
     if (b == null) { 
      if (other.b != null) 
       return false; 
     } else if (!b.equals(other.b)) 
      return false; 
     if (bKey == null) { 
      if (other.bKey != null) 
       return false; 
     } else if (!bKey.equals(other.bKey)) 
      return false; 
     return true; 
    } 
} 

public class B { 

    public A a; 
    public String aKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((a == null) ? 0 : a.hashCode()); 
     result = prime * result + ((aKey == null) ? 0 : aKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof B)) 
      return false; 
     B other = (B) obj; 
     if (a == null) { 
      if (other.a != null) 
       return false; 
     } else if (!a.equals(other.a)) 
      return false; 
     if (aKey == null) { 
      if (other.aKey != null) 
       return false; 
     } else if (!aKey.equals(other.aKey)) 
      return false; 
     return true; 
    } 
} 

vấn đề này được đưa ra về khi tôi đã cố gắng để thêm class A đến một HashSet theo cách sau:

public static void main(String[] args) { 

     A a = new A(); 
     B b = new B(); 
     a.b = b; 
     b.a = a; 

     Set<A> aSet = new HashSet<A>(); 
     aSet.add(a); 
    } 

này sẽ kết thúc trong một StackOverflowError kể từ khi thêm a để aSet sẽ dẫn đến a 'phương thức hashCode s được gọi, mà sẽ dẫn đến b' s hashCode là gọi là, sẽ r esult trong số 's hashCode được gọi, v.v. v.v.Cách duy nhất để giải quyết vấn đề này là xóa tham chiếu đến A từcủa hashCodeequals HOẶC chỉ bao gồm String bKey trong phương thức hashCode của B. Vì tôi muốn phương pháp B.equals để bao gồm tham chiếu A trong việc kiểm tra tính bình đẳng, điều duy nhất tôi có thể làm là làm cho B.hashCode chỉ sử dụng một tập con của các trường được sử dụng trong B.equals tức là chỉ sử dụng B.bKey trong B.hashCode. Tôi không thể nhìn thấy cách nào khác.

Có thể thiết kế của tôi bị thiếu sót và tôi chào mừng ai đó chỉ ra điều đó nhưng về cơ bản là cách đối tượng tên miền của tôi được cấu trúc trong chương trình thực tế của tôi.

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