Eclipse生成的hashCode函数好用吗?

Posted

技术标签:

【中文标题】Eclipse生成的hashCode函数好用吗?【英文标题】:Is the hashCode function generated by Eclipse any good? 【发布时间】:2012-08-01 11:10:38 【问题描述】:

Eclipse 源菜单有一个“生成 hashCode / equals 方法”,它可以生成如下所示的函数。

String name; 
@Override
public int hashCode()

    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;


@Override
public boolean equals(Object obj)

    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    CompanyRole other = (CompanyRole) obj;
    if (name == null)
    
        if (other.name != null)
            return false;
     else if (!name.equals(other.name))
        return false;
    return true;

如果我在生成hashCode()equals() 时选择多个字段,Eclipse 使用上面显示的相同模式。

我不是哈希函数方面的专家,我想知道生成的哈希函数有多“好”?什么情况下它会发生故障并导致过多的碰撞?

【问题讨论】:

我想它已经足够好了。 Joshua Bloch 在他的书 (Effective Java) 中引用了 hashCode() 和 equals() 的相同实现作为示例。 有趣的是,没有一个答案可以解释为什么这段代码是好的。看起来工程师喜欢 Joshua Bloch 的配方,但不幸的是,他似乎是唯一一个明白为什么这个配方很好的人。更新:即使是约书亚也说“即使素数的使用也不太清楚,但它是传统的”。所以我想,即使是他也不得不把这个问题留给其他数学家。 Bloch 还写道“虽然这个配方产生了相当好的哈希函数,但它没有产生最先进的哈希函数,Java 平台库也没有在 1.6 版中提供这样的哈希函数。编写这样的哈希函数是一个研究课题,最好留给数学家和理论计算机科学家。”所以如果你真的想知道为什么,你最好回到大学;) 【参考方案1】:

可以看到java.util.ArrayList中hashCode函数的实现为

public int hashCode() 
    int hashCode = 1;
    Iterator<E> i = iterator();
    while (i.hasNext()) 
        E obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
    
    return hashCode;

这是一个这样的示例,您的 Eclipse 生成的代码遵循类似的实现方式。但是如果你觉得你必须自己实现你的 hashCode,那么 Joshua Bloch 在他的著名著作Effective Java 中给出了一些很好的指导。我将发布该书第 9 项中的重要观点。那些是,

    在名为 result 的 int 变量中存储一些常量非零值,例如 17。

    对于对象中的每个重要字段 f(即 equals 方法考虑的每个字段),请执行以下操作:

    一个。计算字段的 int 哈希码 c:

    我。如果该字段是布尔值,则计算 (f ? 1 : 0)。

    二。如果字段是 byte、char、short 或 int,则计算 (int) f。

    三。如果字段是长的,则计算 (int) (f ^ (f >>> 32))。

    四。如果该字段是浮点数,则计算 Float.floatToIntBits(f)。

    v.如果该字段是双精度字段,则计算 Double.doubleToLongBits(f),然后按照步骤 2.a.iii 对结果进行散列运算。

    六。如果该字段是一个对象引用,并且此类的 equals 方法通过递归调用 equals 来比较该字段,则在该字段上递归调用 hashCode。如果需要更复杂的比较,请为此字段计算“规范表示”并在规范表示上调用 hashCode。如果该字段的值为空,则返回 0(或其他一些常量,但 0 是传统的)

    七。如果该字段是一个数组,则将其视为每个元素都是一个单独的字段。 也就是说,通过应用计算每个重要元素的哈希码 这些规则递归,并在每个步骤 2.b 中组合这些值。如果每个 数组字段中的元素很重要,您可以使用其中一种 1.5 版中添加的 Arrays.hashCode 方法。

    b.将步骤 2.a 中计算的哈希码 c 组合成如下结果:

       result = 31 * result + c;
    

    返回结果。

    写完 hashCode 方法后,问问自己是否 相等的实例具有相等的哈希码。编写单元测试来验证你的直觉! 如果相等的实例具有不相等的哈希码,找出原因并解决问题。

Java 语言设计者和 Eclipse 似乎遵循我想的类似准则。快乐编码。干杯。

【讨论】:

【参考方案2】:

从 Java 7 开始,您可以使用 java.util.Objects 编写简短而优雅的方法:

class Foo 
  private String name;
  private String id;

  @Override
  public int hashCode() 
    return Objects.hash(name,id);
  

  @Override
  public boolean equals(Object obj) 
    if (obj instanceof Foo) 
      Foo right = (Foo) obj;
      return Objects.equals(name,right.name) && Objects.equals(id,right.id);
    
    return false;
  

【讨论】:

【参考方案3】:

一般都不错,但是:

    Guava 以某种方式做到了better,我更喜欢它。 [编辑:从 JDK7 开始,Java 似乎提供了类似的哈希函数]。 某些框架在直接访问字段而不是使用 setter/getter 时可能会导致问题,例如 Hibernate。对于 Hibernate 创建的一些惰性字段,它会创建一个代理而不是真实对象。只有调用 getter 才能让 Hibernate 获取数据库中的实际值。

【讨论】:

你能详细说明一下休眠部分吗? @Eugene: > Guava 在某种程度上做得更好,我更喜欢它。我很好奇 - 有什么更好的方法? @jayeff 不多,你可以在这里找到我的理由:code.google.com/p/guava-libraries/wiki/… 我的类路径上有番石榴,我用它来缓存和不可变集合,但我还没有使用它内置的哈希算法。【参考方案4】:

是的,它是完美的 :) 您将在 Java 源代码中几乎随处看到这种方法。

【讨论】:

您是否尝试过让它为一个表示 id 的单个整数字段生成哈希码?我认为一个简单的“return id”会更合适。【参考方案5】:

这是编写散列函数的标准方法。但是,如果您对这些领域有一些了解,您可以改进/简化它。例如。如果您的类保证该字段永远不会为空(也适用于 equals()),则可以省略空检查。或者,如果只使用一个字段,您可以委托该字段的哈希码。

【讨论】:

【参考方案6】:

我还想添加对 Joshua Bloch 在 Effective Java 2nd Edition 中的第 9 项的引用。

这是来自Item 9 : ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS的食谱

    在名为 result 的 int 变量中存储一些常量非零值,例如 17。 对于对象中的每个重要字段 f(即 equals 方法考虑的每个字段),请执行以下操作:
    a. Compute an int hash code c for the field:            
        i.   If the field is a boolean, compute (f ? 1 : 0).
        ii.  If the field is a byte, char, short, or int, compute (int) f.
        iii. If the field is a long,compute(int)(f^(f>>>32)).
        iv.  If the field is a float, compute Float.floatToIntBits(f).
        v.   If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii.
        vi.  If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
        vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5. 
   b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c;
3. Return result.
4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem.

【讨论】:

【参考方案7】:

如果您使用的是 Apache Software Foundation(commons-lang 库),那么 下面的类将帮助您使用反射生成 hashcode/equals/toString 方法。

添加/删除实例变量时,您无需担心重新生成 hashcode/equals/toString 方法。

EqualsBuilder - 这个类提供了为任何类构建一个好的equals方法的方法。它遵循 Joshua Bloch 在 Effective Java 中制定的规则。特别是比较双精度数、浮点数和数组的规则可能很棘手。此外,确保 equals() 和 hashCode() 一致也很困难。

HashCodeBuilder - 这个类可以为任何类构建一个好的 hashCode 方法。它遵循 Joshua Bloch 的《Effective Java》一书中规定的规则。编写一个好的 hashCode 方法实际上是相当困难的。此类旨在简化流程。

ReflectionToStringBuilder - 此类使用反射来确定要附加的字段。因为这些字段通常是私有的,所以该类使用 AccessibleObject.setAccessible(java.lang.reflect.AccessibleObject[], boolean) 来更改字段的可见性。除非正确设置了适当的权限,否则这将在安全管理器下失败。

Maven 依赖:

<dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>$commons.lang.version</version>
</dependency>

示例代码:

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;

public class Test

    instance variables...
    ....

    getter/setter methods...
    ....

    @Override
    public String toString() 
        return ReflectionToStringBuilder.toString(this);
    

    @Override
    public int hashCode() 
        return HashCodeBuilder.reflectionHashCode(this);
    

    @Override
    public boolean equals(Object obj) 
        return EqualsBuilder.reflectionEquals(this, obj);
    

【讨论】:

【参考方案8】:

一个潜在的缺点是所有具有空字段的对象都将具有 31 的哈希码,因此仅包含空字段的对象之间可能存在许多潜在的冲突。这将使Maps 中的查找速度变慢。

当您的 Map 其键类型具有多个子类时,可能会发生这种情况。例如,如果您有一个HashMap&lt;Object, Object&gt;,您可能有许多哈希码为 31 的键值。诚然,这种情况不会经常发生。如果您愿意,您可以将素数的值随机更改为 31 以外的值,并减少冲突的可能性。

【讨论】:

以上是关于Eclipse生成的hashCode函数好用吗?的主要内容,如果未能解决你的问题,请参考以下文章

hashCode 的值是怎么生成的?对象内存地址吗?

用Case类生成模板代码

hashmap中equals() 和hashcode(),请教

如何一次生成构造函数、getter、setter、equals&hashcode 和 toString?

为啥在 hashCode 中使用素数?

为啥在 hashCode 中使用素数?