为什么我们需要覆盖hashCode和equals?

时间:2015-04-19 21:48:14

标签: java equals hashcode

默认情况下hashCode和equals工作正常。 我已经使用了像HashMap这样的哈希表的对象,而没有覆盖这个方法,而且很好。例如:

public class Main{
public static void main(String[] args) throws Exception{
    Map map = new HashMap<>();
    Object key = new Main();
    map.put(key, "2");
    Object key2 = new Main();
    map.put(key2, "3");
    System.out.println(map.get(key));
    System.out.println(map.get(key2));
}
}

此代码工作正常。默认情况下,hashCode返回object的内存地址,equals检查两个对象是否相同。那么使用这种方法的默认实现有什么问题呢?

4 个答案:

答案 0 :(得分:1)

在您的示例中,只要您想从HashMap中检索某些内容,就需要keykey2,因为它们的equals()与对象标识相同。这使得HashMap完全没用,因为如果没有这两个键,你就无法从中检索任何东西。传递钥匙是没有意义的,因为你也可以传递价值,这同样很尴尬。

现在试着想象一下HashMap实际上有意义的用例。例如,假设您从外部获得String - 有价值的请求,并希望返回,例如,ip-addresses。来自外部的键显然不能与用于设置地图的键相同。因此,您需要一些方法来比较来自外部的请求与您在初始化阶段使用的密钥。这正是equals的优点:它定义了对象的等价关系,这些对象在物理内存中由相同位表示的意义上是不相同的。 hashCodeequals粗略版本,这是快速从HashMap检索值所必需的。

答案 1 :(得分:1)

请注意这个例子来自旧的pdf:

此代码

    public class Name {

private String first, last;

public Name(String first, String last) { this.first = first; this.last = last;

}

public boolean equals(Object o) {

if (!(o instanceof Name)) return false;

Name n = (Name)o;

return n.first.equals(first) && n.last.equals(last);

}

public static void main(String[] args) {

Set s = new HashSet();

s.add(new Name("Donald", "Duck"));

System.out.println(

s.contains(new Name("Donald", "Duck")));

}

}

...并不总是给出相同的结果,因为它在pdf

中有说明
  唐纳德在场上,但是这一套找不到他。 Name类   违反了hashCode合同

因为,在这种情况下,组成对象的字符串有两个,哈希码也应该由这两个元素组成。

要修复此代码,我们应该添加一个hashCode方法:

public int hashCode() { 
return 31 * first.hashCode() + last.hashCode();
}

pdf中的这个问题结尾说我们应该

  覆盖等于

时,

覆盖hashCode

答案 2 :(得分:0)

您的示例不是很有用,因为拥有简单变量会更简单。即,查找地图中的值的唯一方法是保持原始密钥。在这种情况下,您也可以保留该值,而不是首先使用Map。

如果您希望能够创建一个被认为等同于之前使用的密钥的新密钥,则必须提供如何确定等效性。

答案 3 :(得分:0)

鉴于大多数对象从未被要求提供其身份哈希码,因此系统不会为大多数对象保留任何足以建立永久身份的信息。相反,Java在对象头中使用两个位来区分三种状态:

  1. 从未查询过该对象的标识哈希码。

  2. 已查询身份哈希码,但此后该对象尚未被GC移动。

  3. 已查询身份哈希码,此后该对象已被移动。

  4. 对于处于第一状态的对象,请求标识哈希码将对象更改为第二状态并将其作为第二状态对象处理。

    对于处于第二状态的对象,包括之前处于第一状态的对象,将从该地址形成标识哈希码。

    当GC移动处于第二状态的对象时,GC将为该对象分配额外的32位,该对象将用于保存从其原始地址派生的哈希码。然后该对象将被分配到第三个状态。

    对状态3对象的哈希代码的后续请求将使用移动时存储的值。

    当系统知道某个地址范围内的任何对象都处于状态2时,它可能会更改用于计算该范围内地址的哈希码的公式。

    虽然在任何给定时间任何给定地址可能只有一个对象,但完全有可能会要求对象的标识哈希码以后移动,并且另一个对象可能放在同一个地址作为第一个,或者地址将散列到相同的值(系统可能会更改用于计算散列值的公式以避免重复,但无法消除它)。