我们如何决定集合的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的值。
在不同的地方使用不同的乘数是有帮助的,但可能不足以证明额外的工作是合理的。
其他回答
由于您特别要求集合,我想添加一个其他答案还没有提到的方面:HashMap不期望它们的键在添加到集合后改变它们的hashcode。会破坏整个目的…
如果你对dmeister推荐的Effective Java实现感到满意,你可以使用一个库调用来代替自己的调用:
@Override
public int hashCode() {
return Objects.hash(this.firstName, this.lastName);
}
这需要Guava (com.google.common.base.Objects.hashCode)或Java 7中的标准库(Java .util. objects .hash),但工作方式相同。
虽然这链接到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));
}
我更喜欢使用实用工具方法从谷歌集合库从类对象,帮助我保持我的代码干净。equals和hashcode方法通常都是从IDE的模板中创建的,所以它们的可读性不太好。
最好的实现?这是一个很难回答的问题,因为这取决于使用模式。
Josh Bloch的Effective Java在第8项(第二版)中提出了几乎所有情况下合理的良好实现。最好的办法是去查一下,因为作者在那里解释了为什么这种方法是好的。
简短的版本
Create a int result and assign a non-zero value. For every field f tested in the equals() method, calculate a hash code c by: If the field f is a boolean: calculate (f ? 0 : 1); If the field f is a byte, char, short or int: calculate (int)f; If the field f is a long: calculate (int)(f ^ (f >>> 32)); If the field f is a float: calculate Float.floatToIntBits(f); If the field f is a double: calculate Double.doubleToLongBits(f) and handle the return value like every long value; If the field f is an object: Use the result of the hashCode() method or 0 if f == null; If the field f is an array: see every field as separate element and calculate the hash value in a recursive fashion and combine the values as described next. Combine the hash value c with result: result = 37 * result + c Return result
这将导致在大多数使用情况下哈希值的适当分布。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap