hashCode方法的最佳实现

我们如何决定集合的hashCode()方法的最佳实现(假设equals方法已被正确覆盖)?

最好的实现? 这是一个难题,因为它取决于使用模式。

几乎所有情况下, Josh BlochEffective Java中都提出了合理的良好实现。最好的方法是查看它,因为作者解释了为什么这个方法是好的。

简短的版本

  1. 创build一个int result并分配一个非零值。

  2. 对于在equals()方法中testing的每个字段 f ,通过以下方式计算哈希码c

    • 如果字段f是一个boolean :calculate (f ? 0 : 1) ;
    • 如果字段f是一个bytecharshortint :calculate (int)f ;
    • 如果字段f是long :calculate (int)(f ^ (f >>> 32)) ;
    • 如果字段f是一个float :calculate Float.floatToIntBits(f) ;
    • 如果字段f是double :则计算Double.doubleToLongBits(f)并像每个long值一样处理返回值;
    • 如果字段f是一个对象 :使用hashCode()方法的结果,或者如果f == null ,则使用0;
    • 如果字段f是一个数组 :将每个字段视为单独的元素,并以recursion方式计算散列值,并将这些值合并,如下所述。
  3. 结合哈希值cresult

     result = 37 * result + c 
  4. 返回result

这应该导致在大多数使用情况下散列值的正确分布。

如果您对dmeister推荐的有效Java实现感到满意,则可以使用库调用而不是自行滚动:

 @Override public int hashCode(){ return Objects.hashCode(this.firstName, this.lastName); } 

这需要guava( com.google.common.base.Objects.hashCode(...) )或JDK7( java.util.Objects.hash(...) ),但工作方式相同。

最好使用Eclipse提供的function,这个function相当不错,你可以把精力和精力放在开发业务逻辑上。

尽pipe这与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; } 

编辑

通常,当你重写hashcode(...) ,你也想覆盖equals(...) 。 所以对于那些已经或者已经实现了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是正确实现的。 从IBM DeveloperWorks的文章 :

  • 对称性:对于两个引用,a和b,a.equals(b)当且仅当b.equals(a)
  • 反身性:对于所有非空引用,a.equals(a)
  • 传递性:如果a.equals(b)和b.equals(c),那么a.equals(c)

然后确保他们与hashCode的关系尊重联系人(来自同一篇文章):

  • 与hashCode()的一致性:两个相等的对象必须具有相同的hashCode()值

最后,一个好的散列函数应该力求接近理想的散列函数 。

about8.blogspot.com,你说

如果equals()对两个对象返回true,那么hashCode()应该返回相同的值。 如果equals()返回false,那么hashCode()应该返回不同的值

我不能同意你的看法。 如果两个对象具有相同的哈希码,则不一定意味着它们相同。

如果A等于B,则A.hashcode必须等于B.hascode

如果A.hashcode等于B.hascode,则不意味着A必须等于B.

Apache Commons Lang中的Effective Javahashcode()equals()逻辑有很好的实现。 签出HashCodeBuilder和EqualsBuilder 。

如果我正确理解你的问题,你有一个自定义的集合类(即一个从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); } 

如果你使用eclipse,你可以使用下面的代码生成equals()hashCode()

源代码 – >生成hashCode()和equals()。

使用这个函数,你可以决定你想用哪个字段进行相等和散列码计算,并且Eclipse会生成相应的方法。

只需填写其他更详细的答案(根据代码):

如果我考虑如何创build一个哈希表在Java中 ,特别是jGuru常见问题条目 ,我相信一些哈希代码可以判断的其他标准是:

  • 同步(algorithm是否支持并发访问)?
  • 失败安全迭代(algorithm是否检测迭代期间改变的集合)
  • 空值(散列码是否支持集合中的空值)

@约8:那里有一个非常严重的错误。

 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中直接从int获得hashCode吗?我认为它有一些自动生成function。如果是这种情况,跳过toString,这很丑陋。)

正如你特别要求集合,我想添加一个方面,其他答案还没有提到:一个HashMap不希望他们的密钥改变他们的哈希码,一旦他们被添加到集合。 会打败整个目的…

任何散列方法均匀散布散列值在可能的范围是一个很好的实现。 看到有效的Java( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=zh-CN&sa=X&oi=book_result&resnum=1&ct=result ),有一个很好的提示在那里为散列码实现(项目9我认为…)。

我更喜欢使用来自类对象的Google集合库中的实用工具方法,这有助于我保持代码清洁。 经常是equalshashcode方法是从IDE的模板,所以他们是不干净的阅读。

在Apache Commons EqualsBuilder和HashCodeBuilder上使用reflection方法。

我在Arrays.deepHashCode(...)使用了一个很小的包装,因为它正确地处理了作为参数提供的数组

 public static int hash(final Object... objects) { return Arrays.deepHashCode(objects); } 

这是另一个JDK 1.7+方法演示与超级逻辑占。 我认为它很适合于Object class 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()); } } 

对于一个简单的类来说,实现基于由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()应该返回不同的值。

当组合散列值时,我通常使用boost c ++库中使用的组合方法,即:

 seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); 

这在确保均匀分布方面做得相当好。 有关这个公式如何工作的一些讨论,请参阅boost :: hash_combine中的StackOverflow文章: 幻数

http://burtleburtle.net/bob/hash/doobs.html有一个很好的关于不同哈希函数的讨论;