Eclipse生成的hashCode函数是否有用?

时间:2012-08-03 11:45:46

标签: java eclipse hash hashmap hashtable

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使用上面显示的相同模式。

我不是哈希函数的专家,我想知道生成的哈希函数是多么“好”?在什么情况下它会崩溃并导致太多碰撞?

8 个答案:

答案 0 :(得分:17)

您可以在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项中发布那些重要的观点。那些是,

  
      
  1. 将一些常量非零值(例如17)存储在名为result的int变量中。
  2.   
  3. 对于对象中的每个重要字段f(通过equals方法考虑的每个字段,即),执行以下操作:

         

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

         

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

         

    II。如果字段是byte,char,short或int,则为compute(int)f。

         

    III。如果该字段是long,则计算(int)(f ^(f>&gt;&gt;&gt; 32))。

         

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

         

    诉如果该字段是double,则计算Double.doubleToLongBits(f),然后在步骤2.a.iii中对生成的long进行哈希处理。

         

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

         

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

         

    湾将步骤2.a中计算的哈希码c组合到结果中,如下所示:

       result = 31 * result + c;
    
  4.   
  5. 返回结果。

  6.   
  7. 编写完hashCode方法后,问问自己是否   相等的实例具有相同的哈希码。编写单元测试以验证您的直觉!   如果相等的实例具有不相等的哈希码,请弄清楚原因并解决问题。

  8.   

Java语言设计师和Eclipse似乎遵循我想的类似指南。快乐的编码。欢呼声。

答案 1 :(得分:12)

从Java 7开始,您可以使用<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <div id="container"> <input type="button" onclick="highlightSelected()" value="Highlight selected" /> </div>编写简短而优雅的方法:

java.util.Objects

答案 2 :(得分:5)

一般来说这很好,但是:

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

答案 3 :(得分:4)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

以下是来自Item 9 : ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS

的食谱
  
      
  1. 将一些常量非零值(例如17)存储在名为result的int变量中。
  2.   
  3. 对于对象中的每个重要字段f(通过equals方法考虑的每个字段,即),执行以下操作:
  4.   
    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.

答案 6 :(得分:0)

如果您正在使用Apache Software Foundation(commons-lang library) 下面的类将帮助您使用反射生成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);
    }
}

答案 7 :(得分:0)

一个潜在的缺点是,所有具有空字段的对象的哈希码均为31,因此仅包含空字段的对象之间可能存在许多潜在的冲突。这样会使['dfgds', 'asdf', 'bsdf'] 中的查询变慢。

当您的密钥类型具有多个子类的Maps时,可能会发生这种情况。例如,如果您有一个Map,则可能有许多键值,其哈希码为31。不可否认,这种情况不会经常发生。如果愿意,可以将质数的值随机更改为31以外的值,以减少发生碰撞的可能性。