IntelliJ 默认 hashCode() 实现的解释

Explaination for IntelliJ default hashCode() implementation

我查看了 IntelliJ 默认的 hashCode() 实现,想知道为什么他们以这种方式实现它。我对哈希概念很陌生,发现了一些矛盾的陈述,需要澄清:

public int hashCode(){
  // creationDate is of type Date
  int result = this.creationDate != null ? this.creationDate.hashCode() : 0;
  // id is of type Long (wrapper class)
  result = 31 * result + (this.id != null ? this.id.hashCode() : 0);
  // code is of type String
  result = 31 * result + (this.code != null ? this.code.hashCode() : 0);
  // revision is of type int
  result = 31 * result + this.revision;
  return result;
}

Imo,关于这个主题的最佳来源似乎是 this Java world article,因为我发现他们的论点最有说服力。所以我想知道:

  1. 除其他论点外,上述来源指出乘法是较慢的运算之一。那么,每当我调用引用类型的 hashCode() 方法时,跳过与质数的乘法不是更好吗?因为大多数时候这已经包含了这样的乘法。
  2. Java 世界声明按位异或 ^ 由于未提及的原因也改进了计算:(与常规加法相比究竟有什么优势?
  3. 当相应的 class 字段为 null 时,return 不同的值不是更好吗?它会使结果更容易区分,不是吗?使用非 zero 值是否有任何巨大的缺点?

他们的示例代码看起来更吸引我的眼球,说实话:

  public boolean hashCode() {
    return
     (name  == null ? 17 : name.hashCode()) ^ 
     (birth == null ? 31 : name.hashCode());
  }  

但我不确定这是否客观真实。我对 IntelliJ 也有点怀疑,因为它们 equals(Object) 的默认代码通过 instanceof 进行比较,而不是直接比较实例 classes。我同意 Java 世界文章,这似乎没有正确履行合同。

至于 hashCode(),我认为减少冲突(两个不同的对象具有相同的 hashCode())比 hashCode() 计算的速度更重要。是的,hashCode() 应该很快(如果可能的话,恒定时间),但是对于使用 hashCode() 的大型数据结构(映射、集合等),冲突是更重要的因素。

如果您的 hashCode() 函数在恒定时间内执行(与数据和输入大小无关)并产生良好的散列函数(很少发生冲突),则 map 上的操作(get、contains、put)将以恒定时间渐进地执行时间。

如果您的 hashCode() 函数产生大量冲突,性能将受到影响。在极端情况下,您始终可以从 hashCode() return 0 - 函数本身将超快,但地图操作将在线性时间内执行(即随着地图大小增长)。

在添加另一个字段的子 hashCode 之前乘以 hashCode() 通常应该提供较少的冲突 - 这是一种基于字段通常包含相似数据/小数字的启发式算法。

考虑 class 人的例子:

class Person {
    int age;
    int heightCm;
    int weightKg;
}

如果您只是将数字相加来计算哈希码,则所有人的结果将介于 60 到 500 之间。如果你按照 Idea 的方式乘以它,你将得到 2000 到 100000 之间的 hashCodes - 更大 space 因此冲突的可能性更低。

使用 XOR 不是一个好主意,例如,如果您有 class Rectangle 字段 heightwidth,所有方块将具有相同的 hashCode - 0.


至于 equals() 使用 instanceofgetClass().equals(),我从未见过关于此的定论。两者各有优缺点,一不小心就会出事:

  • 如果您使用 instanceof,任何覆盖您的 equals() 的子class 都可能会破坏对称性要求
  • 如果您使用 getClass().equals(),这将不适用于一些框架,例如 Hibernate,它们生成自己的 classes 的子classes 来存储自己的技术信息