具有两个int属性的自定义类的hashCode是什么?

时间:2012-07-31 14:36:54

标签: java equals hashcode

在Java中,我有一个表示int坐标

的点的类
public class Point {
    int x = -1;
    int y = -1;

    public Point (int xNew, int yNew) {
        x = xNew; y = yNew;
    }

    public boolean equals (Object o) {
        // no need for (o instanceof Point) by design
        return x == ((Point)o).x && y == ((Point)o).y;
    }
}

我正在使用Point类的对象作为HashMap中的键和HashSet中的元素。

hashCode函数的最佳候选者是什么?我会把它加倍,以便左边的部分是x而右边的部分是y,例如: x = 4, y = 12,然后hashCode返回4.12。但是通过实现,它不能是双倍的,只有int。

这不是一个选择:

public int hashCode() {
    // no need to check for exception parseInt since x and y are valid by design
    return Integer.parseInt(Integer.toString(x) + Integer.toString(y));
}

因为值xy可能太长,所以它们不会被转换。

8 个答案:

答案 0 :(得分:37)

您无法更改hashCode的类型,也不能更改。

我会选择类似的东西:

public int hashCode() {
    return x * 31 + y;
}

请注意,这意味着(a,b)与大多数情况下的(b,a)不同(与例如添加或异或不同)。这可能很有用如果你经常会得到现实生活中“切换”值的键。

不是唯一 - 但哈希码不一定是。对于相等的值(为了正确性),它们只是必须相同,并且(对于效率)对于非等值,“通常”是不同的,具有合理的分布。

一般来说,我通常遵循Josh Bloch在Effective Java中建议的相同模式:

public int hashCode() {
    int hash = 17;
    hash = hash * 31 + field1Hash;
    hash = hash * 31 + field2Hash;
    hash = hash * 31 + field3Hash;
    hash = hash * 31 + field4Hash;
    ...
    return hash;
}

其中field1Hash将是引用类型字段的哈希码(或0表示空引用),int本身用于int值,某些类型的哈希从64位到32用于{{ 1}}等等。

编辑:我不记得为什么31和17一起工作的细节。事实上,他们都是素数可能有用 - 但从我记忆中,为什么这样的哈希背后的数学通常是合理的(虽然不如哈希那样好可能值的分布是预先已知的)要么难以理解,要么不能很好地理解。我知道乘以31便宜(左移5并减去原值)......

答案 1 :(得分:8)

只需使用java.util.Objects.hash(Object... values)

public int hashCode() {
    return Objects.hash(field1,field2);
}

Objects.hash实际上调用了Arrays.hashCode(Object a[])

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

答案 2 :(得分:7)

我知道非等同对象可以使用相同的哈希码。但是,冲突越多,性能就越差(例如,在哈希表中)。

据我所知, Z ²→ Z 的最佳映射是“优雅的配对功能”(google it)。这是实现

// x,y must be non-negative
int elegant(int x, int y) {
    return x < y ? y * y + x : x * x + x + y;
}


// returns a unique number for every x,y pair
int elegantSigned(int x, int y) {
    if (x < 0) {
        if (y < 0)
            return 3 + 4 * elegant(-x - 1, -y - 1);
        return 2 + 4 * elegant(-x - 1, y);
    }
    if (y < 0)
        return 1 + 4 * elegant(x, -y - 1);
    return 4 * elegant(x, y);
}

一旦出现乘法溢出,这将开始重叠。如果x和y的绝对值小于约46000,那么这将具有哈希冲突。

答案 3 :(得分:3)

经常值得考虑Apache Commons HashCodeBuilder

  

这个类可以为任何类构建一个好的hashCode方法。   它遵循Joshua的书“Effective Java”中规定的规则   布洛赫。编写好的hashCode方法实际上非常困难。   本课程旨在简化流程

我肯定会建议查看引用的书 Effective Java

答案 4 :(得分:2)

有一种生成哈希码操作的常用策略。在你的情况下,这将是:

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + x;
    result = prime * result + y;
    return result;

}

答案 5 :(得分:1)

您可能需要查看Google Guava's Objects.hashCode(Object...)方法。

public int hashCode() {
  return Objects.hashCode(x, y);
}

答案 6 :(得分:1)

这个问题已经很老了,但是我认为只要Java存在,这个想法就可以实现。让我们分析以上方法:

  1. Objects.hashCode(...) 精通并明确需要做什么,但是它使用varargs (隐式创建数组),此外,它隐式框。
  2. x * 31 + y性能有效:没有装箱,没有使用显式或隐式数组创建操作。但是,不清楚需要做什么。为什么是31,而不是42?对于那些熟悉哈希工作原理的人来说,理解这样的代码并不困难,但是对于其他人呢?第二个陷阱是难以扩展:例如,如果您想进行3D并添加 z ,您很容易忘记将新值添加到哈希代码中协调,因为它迫使您多次复制粘贴几乎相同的代码。

我可以介绍第三种方法,上面的答案中没有提及:

@Override
public final int hashCode()
{
    final int[] numbers = {x, y};
    return Arrays.hashCode(numbers);
}

它使用一个临时数组来保存要进行哈希处理的整数,并调用Arrays.hashCode()(自Java 1.5开始可用),还有其他原始类型的版本。

专家:它DRY,流利并且完全清楚需要做什么。它不会遭受隐式装箱,也不会使用隐式vararg。它相对较快且便宜。可以通过在数组初始化程序中添加额外的数字来轻松扩展它。

缺点:它不如复制粘贴方法快。如果经常调用哈希码,请考虑一下。

最诚挚的问候。

答案 7 :(得分:0)

尝试添加他们的哈希码。 ?

返回new Integer(x).hashCode()+ new Integer(y).hashCode();

相关问题