hashCode方法的最佳实现
我们如何决定集合的hashCode()
方法的最佳实现(假设equals方法已被正确覆盖)?
最好的实现? 这是一个难题,因为它取决于使用模式。
几乎所有情况下, Josh Bloch在Effective Java中都提出了合理的良好实现。最好的方法是查看它,因为作者解释了为什么这个方法是好的。
简短的版本
-
创build一个
int result
并分配一个非零值。 -
对于在
equals()
方法中testing的每个字段f
,通过以下方式计算哈希码c
:- 如果字段f是一个
boolean
:calculate(f ? 0 : 1)
; - 如果字段f是一个
byte
,char
,short
或int
:calculate(int)f
; - 如果字段f是
long
:calculate(int)(f ^ (f >>> 32))
; - 如果字段f是一个
float
:calculateFloat.floatToIntBits(f)
; - 如果字段f是
double
:则计算Double.doubleToLongBits(f)
并像每个long值一样处理返回值; - 如果字段f是一个对象 :使用
hashCode()
方法的结果,或者如果f == null
,则使用0; - 如果字段f是一个数组 :将每个字段视为单独的元素,并以recursion方式计算散列值,并将这些值合并,如下所述。
- 如果字段f是一个
-
结合哈希值
c
和result
:result = 37 * result + c
-
返回
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 Java的hashcode()
和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集合库中的实用工具方法,这有助于我保持代码清洁。 经常是equals
和hashcode
方法是从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有一个很好的关于不同哈希函数的讨论;