将对象的哈希码定义为所有类变量哈希码的总和,乘法等是不正确的?
假设我有以下课程:
class ABC { private int myInt = 1; private double myDouble = 2; private String myString = "123"; private SomeRandomClass1 myRandomClass1 = new ... private SomeRandomClass2 myRandomClass2 = new ... //pseudo code public int myHashCode() { return 37 * myInt.hashcode() * myDouble.hashCode() * ... * myRandomClass.hashcode() } }
这是hashCode的正确实现吗? 这不是我通常这样做的方式(我倾向于遵循Effective Java的指导方针),但我总是试图像上面的代码那样做。
谢谢
这取决于你所说的“正确”。 假设你正在使用所有相关的equals()
定义字段的hashCode()
,那么是的,它是“正确的”。 然而,这样的公式可能不会有良好的分布,因此可能会导致更多的碰撞,这将对性能产生不利影响。
以下是Effective Java 2nd Edition的引用,第9项:覆盖hashCode
时始终覆盖hashCode
虽然这个项目中的配方产生了相当好的散列函数,但它不会产生最先进的散列函数,Java平台库也不提供1.6版本的散列函数。 编写这样的哈希函数是一个研究课题,最好留给数学家和计算机科学家。 […尽管如此,]此项目中描述的技术应该适用于大多数应用程序。
它可能不需要很多数学能力来评估你提出的哈希函数有多好,但为什么还要费心呢? 为什么不遵循已被证实在实践中充分certificate的东西呢?
Josh Bloch的食谱
- 将一些常量非零值(例如17)存储在名为
result
的int
变量中。 - 为每个字段计算
int
哈希码c
:- 如果该字段是
boolean
,则计算(f ? 1 : 0)
- 如果字段是
byte, char, short, int
,compute(int) f
- 如果字段是
long
,则计算(int) (f ^ (f >>> 32))
- 如果该字段是
float
,则计算Float.floatToIntBits(f)
- 如果该字段是
double
,则计算Double.doubleToLongBits(f)
,然后对结果long
进行散列,如上所述。 - 如果该字段是一个对象引用,并且该类的
equals
方法通过递归调用equals
比较该字段,则在该字段上递归调用hashCode
。 如果该字段的值为null
,则返回0。 - 如果该字段是数组,则将其视为每个元素都是单独的字段。 如果数组字段中的每个元素都很重要,则可以使用版本1.5中添加的
Arrays.hashCode
方法之一。
- 如果该字段是
- 将哈希码
c
组合成result
如下:result = 31 * result + c;
现在,当然,这个配方相当复杂,但幸运的是,你不必每次都重新实现它,这要归功于java.util.Arrays.hashCode(Object[])
(和com.google.common.base.Objects
提供的一个方便的vararg变种)。
@Override public int hashCode() { return Arrays.hashCode(new Object[] { myInt, //auto-boxed myDouble, //auto-boxed myRandomClass, }); }
也可以看看
-
Object.hashCode()
如果两个对象根据
equals(java.lang.Object)
方法不相等,则不需要在两个对象中的每一个上调用hashCode
方法必须生成不同的整数结果。 但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。
合同允许这样做。 但总是返回1
。 HotSpot中有一个编译时标志,总是为身份哈希值返回1
。 然而,这样的选择会产生不良的表现。
乘法存在一个特殊问题。 不仅来自组件的0哈希值湮灭该值,而且2的幂将逐渐使低位更零。
交换运算符的问题是值的重新排列会导致冲突。
如果组件的哈希值之间存在特定关系,则添加将特别糟糕。 (4, 6)
和(2, 8)
发生冲突。
不,但在实践中几乎肯定不是一个好主意。 最重要的是,您不能修改在哈希代码中使用的任何字段。 他们都必须保持不变。
如果修改了一个,可能会发生这种情况:在HashSet中插入objecy,更改字段,然后测试对象是否在HashSet中。 虽然它在那里,但由于哈希码改变了,HashSet将无法找到它!
在我看来,除非你可以保证产品是素数,否则你可能会在对象的结果哈希码之间发生冲突(尽管可能很少见)