2013-03-26 137 views
7

我现在有一个覆盖equals(Object),看起来像这样:使用equalsIgnoreCase重写的equals重载的hashCode检查平等

@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)方法并生成不违反合约的哈希码?

+0

在hashCode()方法的结果= 31 ...应该是结果* = 31 ......这样你就不会在那里已经失去了价值。 – Patashu 2013-03-26 03:25:05

+1

他导致方程,31 * result +(otherstuff)。所以它不会丢失。我的2美分,但我认为你是在正确的方式。你的equals方法对我来说看起来不错。 – Kyle 2013-03-26 03:27:19

+0

为什么你的代码违反合同?你的直觉一定很紧张,不要听它;) – ddmps 2013-03-26 03:31:30

回答

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将与使用equalsIgnoreCase限定的equals一致。原则上,根据equalsIgnoreCase合同,这似乎依赖于它既然如此那

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

每当

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

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

+0

而'String.compareToIgnoreCase'明确使用这个方法。 – 2013-05-07 15:13:47

+0

我会为新方法+1,但你应该非常小心。 Javadocs甚至警告你:'一般来说,应该使用String.toLowerCase()将字符映射为小写字母。字符串大小写映射方法比字符大小写映射方法有几个优点。字符串大小写映射方法可以执行区域敏感映射,上下文敏感映射和1:M字符映射,而字符大小写映射方法cannot.'此外,这种行为似乎并没有保证规范,所以它可能会改变其他你。警告! – 2013-05-07 18:46:30

+1

对......我会说'字符串。equalsIgnoreCase()'(和'String.compareToIgnoreCase()'),基于'Character'个案映射方法,应该有同样的警告。在编写与'equals()'一致的'hashCode()'方面,您既可以在两者中都使用基于字符的case-mapping,也可以在两者中使用基于String的case-mapping。事实上,最初的提问者可能真的想保留他的'hashCode()'方法并改变他的'equals()'方法来使用's1.toLowerCase()。equals(s2.toLowerCase())'而不是'equalsIgnoreCase )'。 – 2013-05-08 14:47:16

1

你说得对。我们可以遍历所有的单字符串,并找到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

1

你的担心不无道理。 Read the contract for equalsIgnoreCase

两个字符C1和C2被认为是相同的情况下,忽略如果下列中的至少一个为真:

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

所以,如果有一个字符相等时转换为上面案件,但不是相反,你会遇到麻烦。

我们来看一下德国字符ß的示例,该字符在转换为大写字母时会变成two character sequence SS。这意味着字符串“ß”和“SS”是“equalsIgnoreCase”,但在转换为小写时不会具有相同的表示形式!

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

+0

因此,以角色ß为例,如果我们有一个名字为“ßillyßob”的玩家,将他与另一名名为“SSilly SSob”的玩家比较会让他们在equalsIgnoreCase中相等,但之后会生成两个不同的hashCodes(问题)。假设这对我的应用程序来说是'好的',那么我们可以通过使用toUpperCase来使用toLowerCase来生成一个hashCode,当它们被equalsIgnoreCase视为相等时,它们是相等的吗? – Jazzer 2013-03-26 04:59:41

+0

我相信你也可以找到一个反例。 – 2013-03-27 18:40:21

+0

@Jazzer:'equalsIgnoreCase'是否定义了一个等价关系,也就是说它不可能有三个字符串x,y和z,例如x.equalsIgnoreCase(y)和y.equalsIgnoreCase(z),而不是x.equalsIgnoreCase Z)?通过它的声音,“ß”.equalsIgnoreCase(“SS”)将是真实的,并且“ss”.equalsIgnoreCase(“SS”)将是真实的,但是“ß”.equalsIgnoreCase(“ss”)将是错误的。用一个没有实现等价关系的函数覆盖'equals'会被破坏,即使'hashCode'总是返回匹配字符串的匹配值。 – supercat 2013-04-26 21:30:45

1

以书面方式向equals()一个hashCode()一致的方面,您应该使用这两种Character基础的情况下映射,或在两个String基础的情况下映射。在我的其他答案中,我展示了如何使用基于Character的案例映射编写hashCode();但还有另一种解决方案,即将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()); 
} 
+0

在某些情况下,实际上,您确实希望在字符串中使用一些奇特的Unicode规范化以及案例折叠。请参阅http://userguide.icu-project.org/transforms/normalization。 – 2013-05-08 14:54:43

相关问题