6

我正在使用 Java 7,并且下面有以下类。我正确地实现了equalshashCode但问题是在下面的 main 方法中返回却equals为两个对象返回了相同的哈希码。我可以让更多的眼睛来看看这门课,看看我在这里做错了什么吗?falsehashCode

更新:Objects.hash我用自己的哈希函数替换了调用该方法的行: chamorro.hashCode() + english.hashCode() + notes.hashCode(). 它返回一个不同的哈希码,这是hashCode当两个对象不同时应该做的。Objects.hash方法坏了吗?

对你的帮助表示感谢!

import org.apache.commons.lang3.StringEscapeUtils;

public class ChamorroEntry {

  private String chamorro, english, notes;

  public ChamorroEntry(String chamorro, String english, String notes) {
    this.chamorro = StringEscapeUtils.unescapeHtml4(chamorro.trim());
    this.english = StringEscapeUtils.unescapeHtml4(english.trim());
    this.notes = notes.trim();
  }

  @Override
  public boolean equals(Object object) {
    if (!(object instanceof ChamorroEntry)) {
      return false;
    }
    if (this == object) {
      return true;
    }
    ChamorroEntry entry = (ChamorroEntry) object;
    return chamorro.equals(entry.chamorro) && english.equals(entry.english)
        && notes.equals(entry.notes);
  }

  @Override
  public int hashCode() {
    return java.util.Objects.hash(chamorro, english, notes);
  }

  public static void main(String... args) {
    ChamorroEntry entry1 = new ChamorroEntry("Åguigan", "Second island south of Saipan. Åguihan.", "");
    ChamorroEntry entry2 = new ChamorroEntry("Åguihan", "Second island south of Saipan. Åguigan.", "");
    System.err.println(entry1.equals(entry2)); // returns false
    System.err.println(entry1.hashCode() + "\n" + entry2.hashCode()); // returns same hash code!
  }
}
4

4 回答 4

13

其实,你碰巧触发了纯属巧合。:)

Objects.hash碰巧是通过连续添加每个给定对象的哈希码然后将结果乘以 31 来实现的,而String.hashCode对其每个字符也是如此。巧合的是,您使用的“English”字符串中的差异与“Chamorro”字符串中的相同差异发生在距字符串末尾的恰好多一个偏移处,因此一切都完美地抵消了。恭喜!

尝试使用其他字符串,您可能会发现它按预期工作。正如其他人已经指出的那样,严格来说,这种效果实际上并没有错,因为即使哈希码表示的对象不相等,也可能正确地发生冲突。如果有的话,尝试找到更有效的哈希可能是值得的,但我认为在现实情况下几乎没有必要。

于 2012-12-09T07:00:45.313 回答
7

不要求不相等的对象必须具有不同的 hashCode。相等的对象应该具有相等的 hashCodes,但不禁止哈希冲突。 return 1; 如果不是很有用,那将是 hashCode 的完全合法的实现。

毕竟,只有 32 位可能的哈希码和无限数量的可能对象 :) 有时会发生冲突。

于 2012-12-09T06:54:25.287 回答
4

HashCode 是 32 位 int 值,总是有可能发生冲突(两个对象的哈希码相同),但它很少/巧合。你的例子是非常巧合的例子之一。这是解释。

当您调用 时Objects.hash,它会在内部Arrays.hashCode()使用如下逻辑调用:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;
    int result = 1;
    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());
    return result;
}

对于您的 3 参数 hashCode,结果如下:

   31 * (31 * (31 *1 +hashOfString1)+hashOfString2) + hashOfString3

对于您的第一个对象。单个字符串的哈希值是:

查莫罗 --> 1140493257 英文 --> 1698758127 注释 --> 0

对于第二个对象:

查莫罗 --> 1140494218 英文 --> 1698728336 注释 --> 0

如果您注意到,两个对象中哈希码的前两个值是不同的。

但是当它计算最终的哈希码时:

  int hashCode1 = 31*(31*(31+1140493257) + 1698758127)+0;
  int hashCode2 = 31*(31*(31+1140494218) + 1698728336)+0;

巧合的是,它会产生相同的哈希码1919283673,因为int它是以 32 位存储的。

验证您自己使用以下代码段的理论:

  public static void main(String... args) {
    ChamorroEntry entry1 = new ChamorroEntry("Åguigan", 
                         "Second island south of Saipan. Åguihan.", "");
    ChamorroEntry entry2 = new ChamorroEntry("Åguihan", 
                         "Second island south of Saipan. Åguigan.", "");
    System.out.println(entry1.equals(entry2)); // returns false
    System.out.println("Åguigan".hashCode());
    System.out.println("Åguihan".hashCode());
    System.out.println("Second island south of Saipan. Åguihan.".hashCode());
    System.out.println("Second island south of Saipan. Åguigan.".hashCode());
    System.out.println("".hashCode());
    System.out.println("".hashCode());
    int hashCode1 = 31*(31*(31+1140493257) + 1698758127)+0;
    int hashCode2 = 31*(31*(31+1140494218) + 1698728336)+0;
    System.out.println(entry1.hashCode() + "\n" + entry2.hashCode()); 
    System.out.println(getHashCode(
                    new String[]{entry1.chamorro, entry1.english, entry1.notes}) 
                    + "\n" + getHashCode(
                    new String[]{entry2.chamorro, entry2.english, entry2.notes})); 
    System.out.println(hashCode1 + "\n" + hashCode2); // returns same hash code!
  }

    public static int getHashCode(Object a[]) {
        if (a == null)
            return 0;
        int result = 1;
        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());
        return result;
    }

如果你使用一些不同的字符串参数,希望它会导致不同的hashCode。

于 2012-12-09T07:37:33.337 回答
2

两个不相等的对象不必具有不同的哈希值,重要的是两个相等的对象具有相同的哈希值。

我可以像这样实现 hashCode() :

public int hashCode() {
    return 5;
}

它将保持正确(但效率低下)。

于 2012-12-09T06:55:41.513 回答