9

我目前有一个equals(Object)看起来像这样的覆盖:

@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()目前看起来像这样:

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

我的问题是关于我重写的 hashCode() 方法。我知道如果 equals(Object) 方法认为两个对象相等,我需要 hashCode() 返回相同的值。我的直觉告诉我在某些情况下这个 hashCode() 会违反合同。

是否有一种可接受的方式在覆盖的 equals(Object) 方法中使用 equalsIgnoreCase(String) 方法并生成不违反合同的哈希码?

4

4 回答 4

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将与equals定义的 using一致equalsIgnoreCase。原则上,根据 的合同equalsIgnoreCase,这似乎依赖于

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

每当

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

我没有证据证明这是真的,但是equalsIgnoreCase 的 OpenJDK 实现实际上与这种方法一致;它检查对应的字符是否相等,然后检查它们的大写版本是否相等,然后检查大写版本的小写版本是否相等。

于 2013-05-07T14:57:23.767 回答
2

你说的对。我们可以遍历所有单字符字符串,并s1,s2找到s1.equalsIgnoreCase(s2) && !s1.toLowerCase().equals(s2.toLowerCase()). 有相当多的对。例如

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

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

它还取决于语言环境:对于 s1,土耳其语和阿塞拜疆语使用 U+0131 表示小写(参见http://www.fileformat.info/info/unicode/char/0049/index.htm

于 2013-03-26T04:09:36.890 回答
1

在编写hashCode()一致的方面equals(),您应该在两者中使用Character基于案例映射,或者在两者中使用基于案例映射String。在我的另一个答案中,我展示了如何编写基于hashCode()usingCharacter的案例映射;但还有另一种解决方案,即equals()改为使用String基于 - 的案例映射。(请注意,String.equalsIgnoreCase()使用Character基于 - 的案例映射。)

@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());
}
于 2013-05-08T14:53:09.193 回答
1

你担心是对的。 阅读合同equalsIgnoreCase

如果以下至少一项为真,则两个字符 c1 和 c2 被认为是相同的忽略大小写:

  • 这两个字符相同(由 == 运算符比较)
  • 将方法 Character.toUpperCase(char) 应用于每个字符会产生相同的结果
  • 将方法 Character.toLowerCase(char) 应用于每个字符会产生相同的结果

所以,如果有一个字符在转换为大写时是相等的,但不是相反,你就会遇到麻烦。

让我们以德语字符ß为例,它在转换为大写时变成两个字符序列。SS这意味着字符串“ß”和“SS”是“equalsIgnoreCase”,但在转换为小写时不会具有相同的表示!

所以你的方法在这里被打破了。不幸的是,我不确定您是否能够在这里设计一个充分表达您需要的 hashCode。

于 2013-03-26T04:10:19.947 回答