我看了一下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因为我发现他们的论点最有说服力。所以我想知道:
hashCode()
方法时,跳过带素数的乘法不是更好吗?因为大多数时候这已经包含了这样的乘法。 ^
也改善了计算:(与常规添加相比,究竟有什么优势?null
时,返回不同的值会不会更好?它会使结果更加明显,不是吗?使用非zero
值是否有任何巨大的缺点?他们的示例代码看起来更吸引我的眼球,tbh:
public boolean hashCode() {
return
(name == null ? 17 : name.hashCode()) ^
(birth == null ? 31 : name.hashCode());
}
但我不确定这是否客观真实。我也对IntelliJ有点怀疑,因为equals(Object)
的默认代码比较instanceof
而不是直接比较实例类。我同意Java世界文章的说法,这似乎没有正确履行合同。
答案 0 :(得分:4)
对于hashCode()
,我认为最小化冲突(两个具有相同hashCode()的不同对象)比hashCode()计算的速度更重要。是的,hashCode()应该很快(如果可能的话是恒定时间),但是对于使用hashCode()(map,sets等)的大型数据结构,冲突是更重要的因素。
如果你的hashCode()函数在恒定时间内执行(独立于数据和输入大小)并产生良好的散列函数(几个碰撞),渐近地,地图上的操作(get,contains,put)将在恒定时间内执行。
如果你的hashCode()函数产生大量冲突,性能将受到影响。在极端情况下,您始终可以从hashCode()返回0
- 函数本身将超级快,但地图操作将以线性时间执行(即以地图大小增长)。
在添加另一个字段的子hashCode之前乘以hashCode()通常应该提供较少的冲突 - 这是一种基于通常字段包含相似数据/小数字的启发式方法。
考虑一个Person类的例子:
class Person {
int age;
int heightCm;
int weightKg;
}
如果你只是将数字加在一起来计算hashCode,那么对于所有人来说,结果将在60到500之间。如果你按照Idea的方式乘以它,你将获得2000到100000之间的hashCodes - 更大的空间,因此更低的碰撞机会。
使用XOR不是一个好主意,例如,如果您的字段Rectangle
包含字段height
和width
,则所有正方形都具有相同的hashCode - 0
对于使用equals()
与instanceof
的{{1}},我从未见过有关此问题的结论性辩论。两者都有其优点和缺点,如果你不小心,这两种方式都会引起麻烦:
getClass().equals()
,则覆盖instanceof
的任何子类都可能会破坏对称性要求equals()
,这对于像Hibernate这样生成自己的类子类来存储自己的技术信息的框架来说效果不佳