将对象的哈希码定义为所有类变量哈希码的和,乘,等等是不正确的吗?

时间:2010-04-29 05:52:06

标签: c# java .net hashcode

假设我有以下课程:

class ABC {
    private int myInt = 1;
    private double myDouble = 2;
    private String myString = "123";
    private SomeRandomClass1 myRandomClass1 = new ...
    private SomeRandomClass2 myRandomClass2 = new ...

    //pseudo code
    public int myHashCode() {
        return 37 *
               myInt.hashcode() *
               myDouble.hashCode() *
               ... *
               myRandomClass.hashcode()
    }
}

这是hashCode的正确实现吗?这不是我通常这样做的方式(我倾向于遵循Effective Java的指导原则),但我总是试图像上面的代码那样做。

由于

4 个答案:

答案 0 :(得分:13)

这取决于你所说的“正确”。假设您正在使用所有相关hashCode() - 定义字段的equals(),那么是的,它是“正确的”。但是,这些公式可能不会有良好的分布,因此可能会导致更多的碰撞,这将对性能产生不利影响。

以下是来自 Effective Java 2nd Edition 的引用,第9项:覆盖hashCode时始终覆盖equals

  

虽然这个项目中的配方产生了相当好的散列函数,但它不会产生最先进的散列函数,Java平台库也不提供1.6版本的散列函数。编写这样的哈希函数是一个研究课题,最好留给数学家和计算机科学家。 [...尽管如此,]此项目中描述的技术应该适用于大多数应用程序。

评估你提出的哈希函数的好坏可能不需要太多的数学能力,但为什么还要费心呢?为什么不遵循在实践中被证明是充分的事情呢?

Josh Bloch的食谱

  • 将一些常量非零值(例如17)存储在名为int的{​​{1}}变量中。
  • 为每个字段计算result哈希码int
    • 如果字段为c,请计算boolean
    • 如果字段为(f ? 1 : 0),请计算byte, char, short, int
    • 如果字段为(int) f,请计算long
    • 如果字段为(int) (f ^ (f >>> 32)),请计算float
    • 如果该字段为Float.floatToIntBits(f),请计算double,然后对结果Double.doubleToLongBits(f)进行哈希处理,如上所述。
    • 如果该字段是对象引用,并且此类的long方法通过递归调用equals来比较字段,则在该字段上递归调用equals。如果字段的值为hashCode,则返回0.
    • 如果字段是数组,则将其视为每个元素都是单独的字段。如果数组字段中的每个元素都很重要,则可以使用版本1.5中添加的null方法之一。
  • 将哈希码Arrays.hashCode合并到c中,如下所示:result

现在,当然,这个配方相当复杂,但幸运的是,你不必每次都重新实现它,这要归功于java.util.Arrays.hashCode(Object[])(而com.google.common.base.Objects提供了一个方便的变量变体)。

result = 31 * result + c;

另见

  • Object.hashCode()
      

    根据@Override public int hashCode() { return Arrays.hashCode(new Object[] { myInt, //auto-boxed myDouble, //auto-boxed myRandomClass, }); } 方法,如果两个对象不相等则要求,则在两个对象中的每一个上调用equals(java.lang.Object)方法必须生成不同的整数结果。 但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。

答案 1 :(得分:2)

合同允许这样做。但总是返回1。 HotSpot中有一个编译时标志,总是为身份哈希值返回1。然而,这些选择会导致表现不佳。

乘法有一个特殊问题。组件的0哈希值不仅会消除该值,而且2的幂将逐渐将低位写入零。

交换运算符存在重排值会导致冲突的问题。

如果组件的哈希值之间存在特定关系,那么添加将特别糟糕。例如,(4, 6)(2, 8)发生冲突。

答案 2 :(得分:1)

不,但实际上几乎肯定不是一个好主意。最重要的是,您不能修改在哈希代码中使用的任何字段。它们都必须保持不变。

如果修改了一个,可能会发生这种情况:在HashSet中插入objecy,更改字段,然后测试对象是否在HashSet中。虽然它在那里,但由于哈希代码发生了变化,HashSet将无法找到它!

答案 3 :(得分:0)

在我看来,除非你能保证产品是素数,否则你可能会在对象的结果哈希码之间发生碰撞(尽管可能很少见)