为啥子重写equals方法时一定要重写hashCode方法

2,937 阅读3分钟

在每个类中,在重写 equals 方法的时侯,一定要重写 hashcode 方法。如果不这样做,你的类违反了 hashCode的通用约定, 这会阻止它在 HashMapHashSet 这样的集合中正常工作。 根据 Object 规范,以下时具体约定。

  1. 当在一个应用程序执行过程中, 如果在 equals 方法比较中没有修改任何信息, 在一个对象上重复调用 hashCode 方法时,它必须始终返回相同的值。从一个应用程序到另一个应用程序的每一次执行返回的值 可以是不一致的。
  2. 如果两个对象根据 equals(Object) 方法比较是相等的,那么在两个对象上调用 hashCode 就必须产生的 结果是相同的整数。
  3. 如果两个对象根据 equals(Object) 方法比较并不相等,则不要求在每个对象上调用 hashCode 都必须产生不同的结果。但是,程序员应该意识到,为不相等的对象生成不同的结果可能会提高散列表(hash tables) 的性能。

当无法重写 hashCode 时,所违反第二个关键条款是:相等的对象必须具有相等的哈希码(hash code

创建一个Point类,有两个成员变量xy,并重写了equals方法

    public class Point {
        private final int x, y;
    
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (!(obj instanceof Point)) return false;
            Point point = (Point) obj;
            return x == point.x && y == point.y;
        }
    }

在main方法中测试一下

    public static void main(String[] args) {
          Point p1 = new Point(1, 2);
          Point p2 = new Point(1, 2);
          System.out.println(p1.equals(p2));// true
    
          Map<Point, String> map = new HashMap<>();
          map.put(p1, "p1");
          System.out.println(map.get(p2)); // null
    
      }

你可能觉得 map.get(p2) 应该返回字符串 p1, 但是却返回null, 这是因为Point类并没有重写hashCode方法,导致两个相等的实例p1p2返回了不同的哈希码,违反了hashCode的约定,put方法把实例p1放到了一个哈希桶(hash bucket)中,但因为p2哈希码不等于p1哈希码,所以get方法会从其它哈希桶中去查找。

解决这个方法很简单,只需要重写Point类的hashCode方法。

    @Override
    public int hashCode() {
        int result = Integer.hashCode(x);
        result = 31 * result + Integer.hashCode(y);
        return result;
    }

再次测试

    public static void main(String[] args) {
          Point p1 = new Point(1, 2);
          Point p2 = new Point(1, 2);
          System.out.println(p1.equals(p2));// true
    
          Map<Point, String> map = new HashMap<>();
          map.put(p1, "p1");
          System.out.println(map.get(p2)); // p1
      }

这次你会发现map.get(p2) 返回的就是字符串p1了, 因为hashCode这个方法会返回一个简单的确定性计算的结果,它的唯一的输入是 Point实例中的两个重要的属性xy,所以显然相等的 Point实例具有相同的哈希码。

此外Objects 类有一个静态方法,它接受任意数量的对象并为它们返回一个哈希码。这个名为 hash 的方法可以 让你编写一行 hashCode 方法,其质量与根据这个项目中的上面编写的方法相当。

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

注意事项

  • 当你写完 hashCode 方法后,请一定问一下自己是否满足相等的实例有相同的哈希码这一条件。
  • hashCode中涉及到的属性应与equals中保持一致,不要试图从哈希码计算中排除重要的属性来提高性能。

总之,每次重写 equals 方法时都必须重写 hashCode 方法,否则程序将无法正常运行。你的 hashCode 方 法必须遵从 Object 类指定的常规约定,并且必须执行合理的工作,将不相等的哈希码分配给不相等的实例。