我们如何决定集合的hashCode()方法的最佳实现(假设equals方法已被正确重写)?
当前回答
标准实现很弱,使用它会导致不必要的冲突。想象一个
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
Now,
new ListPair(List.of(a), List.of(b, c))
and
new ListPair(List.of(b), List.of(a, c))
List的乘数具有相同的hashCode,即31*(a+b) + c。hashCode在这里被重用。显然,碰撞是不可避免的,但产生不必要的碰撞只是……不必要的。
There's nothing substantially smart about using 31. The multiplier must be odd in order to avoid losing information (any even multiplier loses at least the most significant bit, multiples of four lose two, etc.). Any odd multiplier is usable. Small multipliers may lead to faster computation (the JIT can use shifts and additions), but given that multiplication has latency of only three cycles on modern Intel/AMD, this hardly matters. Small multipliers also leads to more collision for small inputs, which may be a problem sometimes.
使用质数是没有意义的,因为质数在环Z/(2**32)中没有意义。
因此,我建议使用随机选择的大奇数(可以选择质数)。由于i86/amd64 cpu可以使用更短的指令来匹配一个有符号字节的操作数,因此对于像109这样的乘法器来说,速度优势很小。为了最小化冲突,可以使用类似0x58a54cf5的值。
在不同的地方使用不同的乘数是有帮助的,但可能不足以证明额外的工作是合理的。
其他回答
这里有一个非常严重的bug。
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
同样的hashcode
你可能想要
public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(现在你能在Java中直接从int获取hashCode吗?我认为它做了一些自动铸造。如果是这种情况,跳过toString,它很难看。)
关于8.blogspot.com,你说过
如果equals()对于两个对象返回true,那么hashCode()应该返回相同的值。如果equals()返回false,那么hashCode()应该返回不同的值
我不同意你的看法。如果两个对象具有相同的hashcode,并不意味着它们是相等的。
如果A等于B,那么A.hashcode必须等于B.hascode
but
如果A.hashcode等于B.hascode,这并不意味着A必须等于B
我更喜欢使用实用工具方法从谷歌集合库从类对象,帮助我保持我的代码干净。equals和hashcode方法通常都是从IDE的模板中创建的,所以它们的可读性不太好。
对于简单类,通常最容易基于equals()实现检查的类字段实现hashCode()。
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
最重要的是保持hashCode()和equals()的一致性:如果equals()对于两个对象返回true,那么hashCode()应该返回相同的值。如果equals()返回false,那么hashCode()应该返回不同的值。
虽然这链接到Android文档(Wayback Machine)和我自己在Github上的代码,但它一般适用于Java。我的答案是dmeister的答案的扩展,只是代码更容易阅读和理解。
@Override
public int hashCode() {
// Start with a non-zero constant. Prime is preferred
int result = 17;
// Include a hash for each field.
// Primatives
result = 31 * result + (booleanField ? 1 : 0); // 1 bit » 32-bit
result = 31 * result + byteField; // 8 bits » 32-bit
result = 31 * result + charField; // 16 bits » 32-bit
result = 31 * result + shortField; // 16 bits » 32-bit
result = 31 * result + intField; // 32 bits » 32-bit
result = 31 * result + (int)(longField ^ (longField >>> 32)); // 64 bits » 32-bit
result = 31 * result + Float.floatToIntBits(floatField); // 32 bits » 32-bit
long doubleFieldBits = Double.doubleToLongBits(doubleField); // 64 bits (double) » 64-bit (long) » 32-bit (int)
result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));
// Objects
result = 31 * result + Arrays.hashCode(arrayField); // var bits » 32-bit
result = 31 * result + referenceField.hashCode(); // var bits » 32-bit (non-nullable)
result = 31 * result + // var bits » 32-bit (nullable)
(nullableReferenceField == null
? 0
: nullableReferenceField.hashCode());
return result;
}
EDIT
通常,当重写hashcode(…)时,还需要重写equals(…)。所以对于那些将要或已经实现等于的人,这里有一个来自我的Github的很好的参考…
@Override
public boolean equals(Object o) {
// Optimization (not required).
if (this == o) {
return true;
}
// Return false if the other object has the wrong type, interface, or is null.
if (!(o instanceof MyType)) {
return false;
}
MyType lhs = (MyType) o; // lhs means "left hand side"
// Primitive fields
return booleanField == lhs.booleanField
&& byteField == lhs.byteField
&& charField == lhs.charField
&& shortField == lhs.shortField
&& intField == lhs.intField
&& longField == lhs.longField
&& floatField == lhs.floatField
&& doubleField == lhs.doubleField
// Arrays
&& Arrays.equals(arrayField, lhs.arrayField)
// Objects
&& referenceField.equals(lhs.referenceField)
&& (nullableReferenceField == null
? lhs.nullableReferenceField == null
: nullableReferenceField.equals(lhs.nullableReferenceField));
}
推荐文章
- Eclipse调试器总是阻塞在ThreadPoolExecutor上,没有任何明显的异常,为什么?
- Java生成两个给定值之间的随机数
- 如何有效地从数组列表或字符串数组中删除所有空元素?
- 比较JUnit断言中的数组,简洁的内置方式?
- codestyle;把javadoc放在注释之前还是之后?
- 如何在Spring中定义List bean ?
- 将Set<T>转换为List<T>的最简洁的方法
- 在JavaScript中,什么相当于Java的Thread.sleep() ?
- 使用Java重命名文件
- URL从Java中的类路径加载资源
- .toArray(new MyClass[0]) or .toArray(new MyClass[myList.size()])?
- Hibernate中不同的保存方法之间有什么区别?
- Java 8流和数组操作
- Java Regex捕获组
- Openssl不被视为内部或外部命令