使用equalsIgnoreCase覆盖hashCode并重写equals以检查相等性

时间:2013-03-26 03:23:48

标签: java override equals hashcode

我目前有一个被覆盖的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()方法。我知道我需要hashCode()为两个对象返回相同的值,如果它们被equals(Object)方法认为是相等的。我的直觉告诉我有一些的情况,这个hashCode()将违反合同。

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

4 个答案:

答案 0 :(得分: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定义的equalsIgnoreCase一致。原则上,根据equalsIgnoreCase的合同,这似乎依赖于

的情况
Character.toLowerCase(Character.toUpperCase(c1))==Character.toLowerCase(Character.toUpperCase(c2))

每当

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

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

答案 1 :(得分:1)

你是对的。我们可以循环遍历所有one-char字符串,找到s1,s2s1.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

答案 2 :(得分:1)

你担心是对的。 Read the contract for equalsIgnoreCase

如果至少满足下列条件之一,则两个字符c1和c2被视为相同的忽略大小写:

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

所以,如果在转换为 upper 的情况下有一个相同的字符而不是相反的情况,那么你将遇到麻烦。

让我们以德语字符ß为例,转换为大写字母后变为two character sequence SS。这意味着字符串“ß”和“SS”是“equalsIgnoreCase”,但在转换为小写时不会有相同的表示形式!

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

答案 3 :(得分:1)

在编写与hashCode()一致的equals()方面,您应该使用基于Character的案例映射,或基于String的案例映射都。在我的另一个答案中,我展示了如何使用基于hashCode()的案例映射来编写Character;但还有另一个解决方案,即改变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());
}