2013-03-26 18 views
7

Tôi hiện đã có một ghi đè equals(Object) trông như thế này:hashCode Overriding với bằng ghi đè bằng equalsIgnoreCase để kiểm tra bình đẳng

@Override 
public boolean equals(Object o) { 
    if (o == this) return true; 
    if (! (o instanceof Player)) return false; 
    Player p = (Player) o; 
    return getFirstName().equalsIgnoreCase(p.getFirstName()) && 
      getLastName().equalsIgnoreCase(p.getLastName()); 
} 

hashCode() của tôi hiện trông như thế này:

@Override 
public int hashCode() { 
    int result = 17; 
    result = 31 * result + getFirstName().toLowerCase().hashCode(); 
    result = 31 * result + getLastName().toLowerCase().hashCode(); 
    return result; 
} 

Câu hỏi của tôi là liên quan đến phương thức hashCode() được ghi đè của tôi. Tôi biết rằng tôi cần hashCode() để trả về cùng một giá trị cho hai đối tượng nếu chúng được coi là bằng nhau bằng phương thức equals (Object). Đường ruột của tôi cho tôi biết có một số trường hợp trường hợp hashCode() này sẽ vi phạm hợp đồng.

Có cách nào chấp nhận được để sử dụng phương thức equalsIgnoreCase (String) trong phương thức equals được ghi đè (Object) và tạo mã băm không vi phạm hợp đồng không?

+0

Trong hashCode() kết quả = 31 ... nên kết quả * = 31 ... do đó, bạn không bị mất giá trị đã có trong đó. – Patashu

+1

Ông có kết quả trong phương trình, kết quả 31 * (khác). Vì vậy, nó không bị mất. Chỉ cần 2 xu của tôi, nhưng tôi nghĩ bạn đang đi đúng hướng. Phương pháp equals của bạn có vẻ tốt với tôi. – Kyle

+0

Tại sao mã của bạn vi phạm hợp đồng? Ruột của bạn phải lo lắng, đừng nghe nó;) – ddmps

Trả lời

4
@Override 
public int hashCode() { 
    int result = 17; 
    result = 31 * result + characterwiseCaseNormalize(getFirstName()).hashCode(); 
    result = 31 * result + characterwiseCaseNormalize(getLastName()).hashCode(); 
    return result; 
} 

private static String characterwiseCaseNormalize(String s) { 
    StringBuilder sb = new StringBuilder(s); 
    for(int i = 0; i < sb.length(); i++) { 
     sb.setCharAt(i,Character.toLowerCase(Character.toUpperCase(sb.charAt(i)))); 
    } 
    return sb.toString(); 
} 

hashCode này sẽ phù hợp với một equals định sử dụng equalsIgnoreCase.Về nguyên tắc, theo hợp đồng của equalsIgnoreCase, điều này dường như dựa vào nó là trường hợp đó

Character.toLowerCase(Character.toUpperCase(c1))==Character.toLowerCase(Character.toUpperCase(c2)) 

bất cứ khi nào

Character.toLowerCase(c1)==Character.toLowerCase(c2). 

tôi không có bằng chứng cho thấy đó là sự thật, nhưng thực sự OpenJDK implementation of equalsIgnoreCase nó luôn phù hợp với phương pháp này; nó kiểm tra xem các ký tự tương ứng có bằng nhau hay không, sau đó liệu các phiên bản chữ hoa của chúng có bằng nhau hay không, sau đó liệu phiên bản chữ thường của các phiên bản chữ hoa có bằng nhau hay không.

+0

Và 'String.compareToIgnoreCase' sử dụng phương thức này một cách rõ ràng. –

+0

Tôi sẽ +1 cho một cách tiếp cận mới, nhưng bạn nên thực sự cẩn thận. Các Javadocs thậm chí còn cảnh báo bạn: 'Nói chung, String.toLowerCase() nên được sử dụng để ánh xạ các ký tự thành chữ thường. Các phương thức ánh xạ trường hợp chuỗi có nhiều lợi ích hơn các phương thức ánh xạ trường hợp ký tự. Các phương thức ánh xạ trường hợp chuỗi có thể thực hiện ánh xạ nhạy cảm theo vùng, ánh xạ nhạy cảm theo ngữ cảnh và ánh xạ ký tự 1: M, trong khi các phương thức ánh xạ ký tự không thể.' Cộng với hành vi này dường như không được đảm bảo bởi thông số, vì vậy nó có thể thay đổi từ khác bạn. Thận trọng! –

+1

Phải ... Tôi sẽ nói 'Chuỗi.equalsIgnoreCase() '(và' String.compareToIgnoreCase() '), được dựa trên các phương thức ánh xạ trường hợp' Character', nên đi kèm với cảnh báo tương tự. Về mặt viết một 'hashCode()' nhất quán với 'equals()', bạn nên sử dụng 'ánh xạ trường hợp dựa trên ký tự' trong cả hai, hoặc ánh xạ trường hợp dựa trên 'String' trong cả hai. Trong thực tế, người hỏi ban đầu có thể thực sự muốn giữ phương thức 'hashCode()' của mình và thay đổi phương thức 'equals()' của mình để sử dụng 's1.toLowerCase(). Bằng (s2.toLowerCase())' thay vì 'equalsIgnoreCase () '. –

1

Bạn nói đúng. Chúng ta có thể lặp qua tất cả các chuỗi một char và tìm các cặp s1,s2 rằng s1.equalsIgnoreCase(s2) && !s1.toLowerCase().equals(s2.toLowerCase()). Có khá nhiều cặp. Ví dụ

s1=0049 'LATIN CAPITAL LETTER I' 
s2=0131 'LATIN SMALL LETTER DOTLESS I' 

s1.lowercase = 0069 'LATIN SMALL LETTER I' 
s2.lowercase = 0131 itself 

Nó cũng phụ thuộc vào ngôn ngữ: cho s1, Thổ Nhĩ Kỳ và Azerbaijan sử dụng U + 0131 cho chữ thường (xem http://www.fileformat.info/info/unicode/char/0049/index.htm)

1

Bạn nói đúng là lo lắng. Read the contract for equalsIgnoreCase.

Hai nhân vật c1 và c2 được coi là trường hợp bỏ qua cùng nếu ít nhất một trong các cách sau đây là đúng:

  • Hai nhân vật đều giống nhau (như so sánh bằng các toán tử ==)
  • Áp dụng phương pháp Character.toUpperCase (char) cho mỗi nhân vật tạo ra kết quả tương tự
  • Áp dụng phương pháp Character.toLowerCase (char) cho mỗi nhân vật tạo ra kết quả tương tự

Vì vậy, nếu có một nhân vật bằng nhau khi chuyển đổi thành upper trường hợp nhưng không phải là cách khác xung quanh, bạn sẽ gặp rắc rối.

Hãy lấy ví dụ về ký tự Đức ß, biến thành two character sequence SS khi được chuyển đổi thành chữ hoa. Điều đó có nghĩa là chuỗi "ß" và "SS" là "equalsIgnoreCase" nhưng sẽ không có cùng biểu diễn khi được chuyển đổi thành chữ thường!

Vì vậy, cách tiếp cận của bạn ở đây bị hỏng. Thật không may, tôi không chắc chắn rằng bạn sẽ có thể thiết kế một hashCode đáp ứng đầy đủ nhu cầu của bạn ở đây.

+0

Vì vậy, sử dụng ký tự ß làm ví dụ, nếu chúng ta có một cầu thủ có họ/tên "ßilly ßob", so sánh anh ta với người chơi khác có tên "SSilly SSob" sẽ làm cho chúng ngang nhau trong mắt của equalsIgnoreCase nhưng sau đó tạo ra hai hashCodes khác nhau (vấn đề). Giả sử điều này là 'okay' cho ứng dụng của tôi, chúng ta có thể tạo ra một hashCode bằng nhau khi chúng được cân bằng bằng equalsIgnoreCase bằng cách sử dụng toUpperCase mà tôi đang sử dụng toLowerCase không? – Jazzer

+0

Tôi chắc chắn bạn cũng có thể tìm thấy một cách ngược lại theo cách khác. –

+0

@ Jazzer: Liệu 'equalsIgnoreCase' có định nghĩa một mối quan hệ tương đương hay không, tức là không thể có ba x, y và z, như x.equalsIgnoreCase (y) và y.equalsIgnoreCase (z), nhưng không x.equalsIgnoreCase (z)? Bởi âm thanh của nó, "ß" .equalsIgnoreCase ("SS") là đúng, và "ss" .equalsIgnoreCase ("SS") là đúng, nhưng "ß" .equalsIgnoreCase ("ss") sẽ là false. Để ghi đè 'bằng' với hàm không thực hiện quan hệ tương đương sẽ bị hỏng, ngay cả khi' hashCode' luôn trả về giá trị khớp cho chuỗi phù hợp. – supercat

1

Xét về viết một hashCode() phù hợp với equals(), bạn nên sử dụng một trong hai Character dựa trên trường hợp lập bản đồ trong cả hai, hoặc String dựa trên trường lập bản đồ trong cả hai. Trong câu trả lời khác của tôi, tôi đã cho thấy cách viết một hashCode() bằng cách sử dụng Character dựa trên trường hợp-ánh xạ; nhưng có một giải pháp khác, đó là thay đổi equals() thay vì sử dụng String dựa trên trường hợp-ánh xạ. (Lưu ý rằng String.equalsIgnoreCase() sử dụng Character dựa trên trường lập bản đồ.)

@Override 
public boolean equals(Object o) { 
    if (o == this) return true; 
    if (! (o instanceof Player)) return false; 
    Player p = (Player) o; 
    return getFirstName().toLowerCase().equals(p.getFirstName().toLowerCase()) && 
     getLastName().toLowerCase().equals(p.getLastName().toLowerCase()); 
} 
+0

Trong một số trường hợp, trên thực tế, bạn thực sự muốn sử dụng một số Unicode chuẩn hóa ưa thích trên Strings của bạn cũng như trường hợp gấp. Xem http://userguide.icu-project.org/transforms/normalization. –