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