3

为什么重写equals必须重写hashCode

 2 years ago
source link: https://segmentfault.com/a/1190000041084056
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

equals常见面试题

在开始聊之前,我们先看几个常见的面试题,看看你能不能都回答上来。

  • 1、equals和==有什么区别?
  • 2、hashcode相等的两个对象一定==相等吗?equals相等吗?
  • 3、两个对象用equals比较相等,那它们的hashcode相等吗?

如果我们不重写equals和hashcode,那么它使用的是Object方法的实现。我们先简单看一下

public boolean equals(Object obj) {
    return (this == obj);
}
public static int hashCode(Object o) {
    return o != null ? o.hashCode() : 0;
}

为什么要重写equals

通过以上代码可以看出,Object提供的equals在进行比较的时候,并不是进行值比较,而是内存地址的比较。由此可以知晓,要使用equals对对象进行比较,那么就必须进行重写equals。

重写equals不重写hashCode会存在什么问题

我们先看下面这段话

每个覆盖了equals方法的类中,必须覆盖hashCode。如果不这么做,就违背了hashCode的通用约定,也就是上面注释中所说的。进而导致该类无法结合所以与散列的集合一起正常运作,这里指的是HashMap、HashSet、HashTable、ConcurrentHashMap。
来自 Effective Java 第三版

结论:如果重写equals不重写hashCode它与散列集合无法正常工作。

既然这样那我们就拿我们最熟悉的HashMap来进行演示推导吧。我们知道HashMap中的key是不能重复的,如果重复添加,后添加的会覆盖前面的内容。那么我们看看HashMap是如何来确定key的唯一性的。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

查看代码发现,它是通过计算Map key的hashCode值来确定在链表中的存储位置的。那么这样就可以推测出,如果我们重写了equals但是没重写hashCode,那么可能存在元素重复的矛盾情况。

下面我们来演示一下

public class Employee {

private String name;

private Integer age;

public Employee(String name, Integer age) {
    this.name = name;
    this.age = age;
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Employee employee = (Employee) o;
    return Objects.equals(name, employee.name) &&
            Objects.equals(age, employee.age);
}

/*@Override
public int hashCode() {
    return Objects.hash(name, age);
}*/
}
public static void main(String[] args) {

    Employee employee1 = new Employee("冰峰", 20);
    Employee employee2 = new Employee("冰峰", 22);
    Employee employee3 = new Employee("冰峰", 20);

    HashMap<Employee, Object> map = new HashMap<>();

    map.put(employee1, "1");
    map.put(employee2, "1");
    map.put(employee3, "1");

    System.out.println("equals:" + employee1.equals(employee3));
    System.out.println("hashCode:" + (employee1.hashCode() == employee3.hashCode()));
    System.out.println(JSONObject.toJSONString(map));
}

按正常情况来推测,map中只存在两个元素,employee2和employee3。

执行结果

出现这种问题的原因就是因为没有重写hashCode,导致map在计算key的hash值的时候,绝对值相同的对象计算除了不一致的hash值。


接下来我们打开hashCode的注释代码,看看执行结果

如果重写了equals就必须重写hashCode,如果不重写将引起与散列集合(HashMap、HashSet、HashTable、ConcurrentHashMap)的冲突。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK