集合的hashCode方法的最佳实现

Omn*_*ent 292 java hash equals hashcode

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

dme*_*ter 430

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

对于几乎所有情况,在Josh Bloch的第8版(第二版)的 Effective Java中提出了合理的良好实现.最好的方法是在那里查找,因为作者解释了为什么这种方法很好.

一个简短的版本

  1. 创建一个int result并指定一个非零值.

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

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

    result = 37 * result + c
    
    Run Code Online (Sandbox Code Playgroud)
  4. 返回 result

这应该导致大多数使用情况的哈希值的适当分布.

  • 是的,我特别好奇37号的来源. (44认同)
  • @dma_k使用素数的原因和本答案中描述的方法是确保**计算的哈希码是唯一的**.使用非素数时,您无法保证这一点.你选择哪个主要数字并不重要,数字37没有什么神奇之处(太糟糕42不是素数,是吗?) (39认同)
  • @SimonAndréForsberg好吧,计算的哈希码不能总是唯一的:)是一个哈希码.但我得到了这个想法:素数只有一个乘数,而非素数至少有两个.这为乘法运算符创建了一个额外的组合,以产生相同的散列,即导致冲突. (34认同)
  • 我使用了Josh Bloch的"Effective Java"一书中的第8项. (17认同)
  • 我认为[Bloch乘以31而不是37](http://stackoverflow.com/a/5155015/1028230),因为[它易于优化](http://stackoverflow.com/questions/5154970/how-do -i-创建-A-哈希码功能于净-C-FOR-A-字符串即-是安全的对店中一个/ 5155015?noredirect = 1个#comment51056393_5155015). (13认同)
  • 我不知道有任何证据.37的数量是任意的,但它应该是素数.为什么?我不太确定,但它与模数关节炎和素数的属性有关,导致分布. (11认同)
  • 仅供参考:这是Effective Java第二版中的第9项. (5认同)
  • "true"映射到"0"和"false"到"1"的任何特殊原因,而不是相反? (2认同)
  • @afsantos:我想不出来.本书中的代码更像是一个法律的一般准则,绝不是唯一正确的方法. (2认同)
  • +1优秀的解释,我真的很喜欢你在有人想要更多信息的情况下给出了源(即Bloch的书,而不是源代码). (2认同)
  • Hej @dmeister - 我坚持自己的立场.这很简单.1.散列函数的主要目标(在数据结构中,而不是在加密中)是速度 - 以避免线性搜索的成本.2.计算哈希值的成本与线性搜索的成本之间存在权衡,而对于多字段结构,即使只搜索正确的密钥也足以充分降低成本.3.对于大型数据结构(如我的形式参数),如果您测量"每个字段",计算的数量将在几十个中.没有普遍的答案 - 所以你必须衡量. (2认同)

bac*_*car 135

如果您对dmeister推荐的Effective Java实现感到满意,可以使用库调用而不是自己编译:

@Override
public int hashCode() {
    return Objects.hashCode(this.firstName, this.lastName);
}
Run Code Online (Sandbox Code Playgroud)

这需要Guava(com.google.common.base.Objects.hashCode)或Java 7(java.util.Objects.hash)中的标准库,但工作方式相同.

  • 除非有充分的理由不使用这些,否则无论如何都应该使用这些.(制定它更强,因为它应该制定恕我直言.)使用标准实现/库的典型论据适用(最佳实践,经过良好测试,不易出错等). (8认同)
  • @ justin.hughey你好像很困惑.你应该覆盖`hashCode`的唯一情况是你有一个自定义`equals`,这正是这些库方法的设计目的.关于`equals`的行为,文档非常明确.库实现并不声称可以让您不知道正确的`hashCode`实现的特征是什么 - 这些库使得*更容易*为大多数`equals`被覆盖的情况实现这样的符合实现. (7认同)
  • 对于任何查看java.util.Objects类的Android开发人员来说,它仅在API 19中引入,因此请确保您在KitKat或更高版本上运行,否则您将获得NoClassDefFoundError. (6认同)
  • 最佳答案IMO,尽管以示例方式,我宁愿选择JDK7`java.util.Objects.hash(...)`方法,也不愿选择guava`com.google.common.base.Objects.hashCode(... )`方法。我认为大多数人会选择标准库而不是额外的依赖项。 (2认同)
  • 如果有两个或更多的参数,如果它们中的任何一个是数组,结果可能不是你所期望的,因为数组的`hashCode()`只是它的`java.lang.System.identityHashCode(...)` . (2认同)

War*_*ior 59

最好使用Eclipse提供的功能,它可以很好地工作,您可以将您的精力和精力投入到开发业务逻辑中.

  • 很抱歉,但是涉及"[某些IDE]提供的功能"的答案在编程语言的上下文中并不是真正相关的.有几十个IDE,但这并没有回答这个问题......因为这更多是关于算法的确定,而是与equals()实现直接相关 - 这是IDE一无所知的. (14认同)
  • +1一个很好的实用解决方案 dmeister的解决方案更全面,但当我尝试自己编写哈希码时,我倾向于忘记处理空值. (4认同)
  • +1 同意 Quantum7,但我想说了解 Eclipse 生成的实现正在做什么以及它从哪里获取实现细节也非常好。 (2认同)

Chr*_*ski 57

虽然这与Github Android上的文档(Wayback Machine)我自己的代码相关联,但它通常适用于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;

}
Run Code Online (Sandbox Code Playgroud)

编辑

通常,当您覆盖时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));
}
Run Code Online (Sandbox Code Playgroud)


Gre*_*her 17

首先确保正确实现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()值

最后,一个好的哈希函数应该努力接近理想的哈希函数.


小智 11

你说的是about8.blogspot.com

如果equals()为两个对象返回true,则hashCode()应该返回相同的值.如果equals()返回false,则hashCode()应返回不同的值

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

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

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

  • 如果`(A!= B)和(A.hashcode()== B.hashcode())`,那就是我们所说的哈希函数碰撞.这是因为哈希函数的codomain总是有限的,而它的域通常不是.密码子越大,碰撞发生的频率就越低.好的散列函数应该为不同的对象返回不同的散列,给定特定的codomain大小,可以实现最大的可能性.尽管如此,它很少得到充分保证. (3认同)

Rud*_*nto 7

有一个很好的贯彻实施有效的Javahashcode()equals()逻辑的Apache Commons Lang中.Checkout HashCodeBuilderEqualsBuilder.


Joh*_*ert 7

如果你使用eclipse,你可以生成equals()hashCode()使用:

Source - >生成hashCode()和equals().

使用此函数,您可以决定要使用哪些字段进行相等性和哈希码计算,Eclipse会生成相应的方法.


Von*_*onC 6

简要说明一下完成其他更详细的答案(根据代码):

如果我考虑如何在Java中创建哈希表(尤其是jGuru FAQ条目)问题,我认为可以判断哈希码的其他一些标准是:

  • 同步(算法是否支持并发访问)?
  • 故障安全迭代(算法是否检测到在迭代过程中发生更改的集合)
  • 空值(哈希码是否支持集合中的空值)