Effective Java 11.覆盖equals时总要覆盖hashCode

121 阅读3分钟

覆盖 equals 时总要覆盖 hashCode

在每个覆盖了 equals 方法的类中,都必须覆盖 hashCode 方法。如果不这么做,就违反了 hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合(例如 HashMap 和 HashSet)一起正常使用

Object 中 hashCode 的规范

  • 在应用程序执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,同一个对象的多次调用,hashCode 方法都必须返回相同的值。

  • 如果两个对象根据 equals(Object) 方法比较是相等的,那么调用这两个对象中的 hashCode 方法都必须产生同样的整数效果

  • 如果两个对象根据 equals(Object) 方法比较不相等,那么调用这两个对象中的 hashCode方法,则不一定要求 hashCode 方法必须产生不同的结果

public static void main(String[] args) {
    String s1 = "FB";
    String s2 = "Ea";
    System.out.println(s1.hashCode());
    System.out.println(s2.hashCode());
}

image.png

如果不遵守规范,不覆盖 hashCode呢?

  • HashCode 不相同的情况

没有覆盖 hashCode 而违反的关键约定是第二条:相同的对象必须具有相同的 hashCode。根据类的 equals 方法,两个截然不同的实例在逻辑上有可能是相等的,但根据 Object 类的 hashCode 方法,它们仅仅是两个没有任何共同之处的对象。这句话不太容易懂,看示例代码:

public class Address implements Cloneable {
    private String name;

   	// 重写equals方法,永远返回true
    @Override
    public boolean equals(Object obj) {
        return true;
    }

    // 省略构造函数、Getter&Setter方法
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}


public class Main{
    public static void main(String[] args){
        Address address3 = new Address();
        Address address4 = new Address();
        System.out.println(address3.equals(address4));
        System.out.println(address3.hashCode());
        System.out.println(address4.hashCode());
    }
}

image-20230604165111316转存失败,建议直接上传图片文件

按上述代码中 equals 方法永远返回 true,但两个对象的 hashCode 值是完全不相同的。对象的 hashCode 返回两个看起来随机的整数,而不是根据第二个约定要求的,返回两个相等的整数。

可是,这样会造成什么样的后果呢?

public static void main(String[] args) {
    Map<Address,String> map = new HashMap<>();
    map.put(new Address("name"),"测试通过");
    String s = map.get(new Address("name"));
    System.out.println(s);
}

通过上面的 Address 类进行赋值 name 属性装进一个 HashMap 中,理论上的结果应该打印出 测试通过,但实际上是 null。由于没有覆盖 hashCode 方法导致两个相同的实例有不同的 hashCode,违反了 hashCode 的约定。

  • HashCode都相同的情况

为了解决两个相同对象 hashCode 不相同的情况,我们可以将 hashCode 方法重写,然后返回完全相同的固定数字,这样又会有什么样的问题呢?

还是同样使用 HashMap,因为每个对象的 hashCode 都是一样的,会导致每个对象都被映射到同一个 key 的桶中,会影响map 的 get 方法效率,严重的可能会使 桶 中的链表结构退化成红黑树。

如何重写一个好的 hashCode 方法?

一个好的散列函数通常倾向于 “ 为不相等的对象产生不相等的散列码 ”。理想情况下,散列函数应该把集合中不相等的实例均匀分布到所有可能的整数值上。想要达成这样的目的其实比较难,但实际开发中基本上没有这种需求让我们重写 hashCode 方法,所以还是那句话,用 lombok 可以直接用内置的注解 @EqualsAndHashCode

参考文献