我们如何确定集合的hashCode()
方法的最佳实现(假设已正确覆盖equals方法)?
答案 0 :(得分:421)
最佳实施?这是一个难题,因为它取决于使用模式。
对于几乎所有情况,在项目8(第二版)中 Josh Bloch 的 Effective Java 中提出了合理的良好实施。最好的办法是在那里查找,因为作者解释了为什么这种方法很好。
创建int result
并指定非零值。
对于在f
方法中测试的每个字段 equals()
,请按以下方式计算哈希码c
:
boolean
:
计算(f ? 0 : 1)
; byte
,char
,short
或int
:则计算(int)f
; long
:计算(int)(f ^ (f >>> 32))
; float
:计算Float.floatToIntBits(f)
; double
:计算Double.doubleToLongBits(f)
并像每个长值一样处理返回值; hashCode()
方法的结果,如果f == null
,则使用0; 将哈希值c
与result
:
result = 37 * result + c
返回result
这应该导致在大多数使用情况下正确分配哈希值。
答案 1 :(得分:132)
如果您对dmeister推荐的Effective Java实现感到满意,可以使用库调用而不是自己编译:
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
这需要Guava(com.google.common.base.Objects.hashCode
)或Java 7中的标准库(java.util.Objects.hash
),但工作方式相同。
答案 2 :(得分:58)
最好使用Eclipse提供的功能,这项功能非常出色,您可以将精力和精力投入到开发业务逻辑中。
答案 3 :(得分:54)
虽然这与Android
documentation (Wayback Machine)和My own code on Github相关联,但它通常适用于Java。我的回答是dmeister's Answer的扩展,只是代码更容易阅读和理解。
@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;
}
修改强>
通常,当您覆盖hashcode(...)
时,您还要覆盖equals(...)
。那么对于那些已经或已经实施equals
的人来说,这是一个很好的参考from my 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));
}
答案 4 :(得分:17)
首先确保正确实现了equals。来自an IBM DeveloperWorks article:
- 对称性:对于两个引用,a和b,a.equals(b)当且仅当b.equals(a)
- 自反性:对于所有非空引用,a.equals(a)
- 及物性:如果a.equals(b)和b.equals(c),则a.equals(c)
然后确保他们与hashCode的关系尊重联系人(来自同一篇文章):
- 与hashCode()的一致性:两个相等的对象必须具有相同的hashCode()值
最后一个好的哈希函数应该努力接近ideal hash function。
答案 5 :(得分:11)
about8.blogspot.com,你说
如果equals()为两个对象返回true,则hashCode()应该返回相同的值。如果equals()返回false,则hashCode()应返回不同的值
我不能同意你的看法。如果两个对象具有相同的哈希码,则不一定意味着它们是相等的。
如果A等于B,那么A.hashcode必须等于B.hascode
但
如果A.hashcode等于B.hascode,则并不意味着A必须等于B
答案 6 :(得分:7)
Apache Commons Lang中有 Effective Java 的hashcode()
和equals()
逻辑的良好实现。结帐HashCodeBuilder和EqualsBuilder。
答案 7 :(得分:7)
如果你使用eclipse,你可以使用:
生成equals()
和hashCode()
来源 - >生成hashCode()和equals()。
使用此函数,您可以决定要用于等式和哈希码计算的字段,Eclipse会生成相应的方法。
答案 8 :(得分:5)
快速填写其他更详细的答案(代码方面):
如果我考虑问题how-do-i-create-a-hash-table-in-java,尤其是jGuru FAQ entry,我相信可以判断哈希码的其他一些标准是:
答案 9 :(得分:4)
如果我理解你的问题,你有一个自定义集合类(即从Collection接口扩展的新类),你想实现hashCode()方法。
如果你的集合类扩展了AbstractList,那么你不必担心它,已经有一个equals()和hashCode()的实现,它通过遍历所有对象并将它们的hashCodes()加在一起来工作。
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
现在,如果你想要的是计算特定类的哈希码的最佳方法,我通常使用^(按位异或)运算符来处理我在equals方法中使用的所有字段:
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
答案 10 :(得分:2)
@ about8:那里有一个非常严重的错误。
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
相同的哈希码
你可能想要像这样的东西public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(这些天你能直接从Java中获取hashCode吗?我认为它会进行一些自动排序..如果是这种情况,请跳过toString,这很难看。)
答案 11 :(得分:2)
正如您特别要求收集的那样,我想添加一个其他答案尚未提及的方面:HashMap不希望他们的密钥在添加到集合后更改其哈希码。会打败整个目的......
答案 12 :(得分:2)
在Apache Commons EqualsBuilder和HashCodeBuilder上使用反射方法。
答案 13 :(得分:1)
标准实现很弱,使用它会导致不必要的冲突。想象一下
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);
}
...
}
现在,
new ListPair(List.of(a), List.of(b, c))
和
new ListPair(List.of(b), List.of(a, c))
具有相同的hashCode
,即31*(a+b) + c
,因为List.hashCode
使用的乘数会在此处重复使用。显然,碰撞是不可避免的,但产生不必要的碰撞只是......不必要。
使用31
并没有什么明智之处。乘数必须是奇数,以避免丢失信息(任何偶数乘数至少失去最高有效位,四倍数减去两个,等等)。任何奇数乘数都是可用的。小乘数可能会导致更快的计算(JIT可以使用移位和加法),但考虑到乘法在现代Intel / AMD上只有三个周期的延迟,这几乎不重要。小乘数也会导致小输入的更多碰撞,这有时可能是一个问题。
使用素数是没有意义的,因为素数在环Z /(2 ** 32)中没有意义。
所以,我建议使用一个随机选择的大奇数(随意取一个素数)。由于i86 / amd64 CPU可以使用较短的指令来操作适合单个有符号字节的操作数,因此像109这样的乘法器有一个很小的速度优势。为了最大限度地减少冲突,请使用类似0x58a54cf5的内容。
在不同的地方使用不同的乘数是有帮助的,但可能不足以证明其他工作的合理性。
答案 14 :(得分:1)
这是另一个JDK 1.7+方法演示,其中包含超类逻辑。我认为它非常方便Object类hashCode()占用,纯JDK依赖,没有额外的手工工作。请注意Objects.hash()
是空容忍的。
我没有包含任何equals()
实施,但实际上你当然需要它。
import java.util.Objects;
public class Demo {
public static class A {
private final String param1;
public A(final String param1) {
this.param1 = param1;
}
@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
this.param1);
}
}
public static class B extends A {
private final String param2;
private final String param3;
public B(
final String param1,
final String param2,
final String param3) {
super(param1);
this.param2 = param2;
this.param3 = param3;
}
@Override
public final int hashCode() {
return Objects.hash(
super.hashCode(),
this.param2,
this.param3);
}
}
public static void main(String [] args) {
A a = new A("A");
B b = new B("A", "B", "C");
System.out.println("A: " + a.hashCode());
System.out.println("B: " + b.hashCode());
}
}
答案 15 :(得分:1)
我在Arrays.deepHashCode(...)
周围使用一个小包装器,因为它处理作为参数正确提供的数组
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
答案 16 :(得分:1)
我更喜欢使用来自对象的 Google Collections lib中的实用程序方法来帮助我保持代码清洁。通常equals
和hashcode
方法是从IDE的模板制作的,因此它们不易阅读。
答案 17 :(得分:1)
任何散列方法都可以在可能的范围内均匀分布散列值,这是一个很好的实现。请参阅有效的java(http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result),其中有一个很好的提示,用于哈希码实现(我认为第9项)。
答案 18 :(得分:0)
组合哈希值时,我通常使用boost c ++库中使用的组合方法,即:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
这确保了均匀分布的相当好。有关此公式如何工作的一些讨论,请参阅StackOverflow帖子:Magic number in boost::hash_combine
上讨论了不同的哈希函数答案 19 :(得分:-1)
对于一个简单的类,通常最容易根据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()应该返回不同的值。