hashcode和equals方法的区别与联系
对象的 hashCode 和 equals 方法特点
对于 hashCode 和 equals 方法,我们有以下三个结论:
结论 1:hashcode 相等,equals 不一定相等。
结论 2:equals 相等,hashcode 一定相等。
结论 3:hashcode 不相等,equals 一定不相等。
为什么需要 hashCode 和 equals 方法?
hashCode() 方法和 equal() 方法的作用其实一样,在 Java 里都是用来对比两个对象是否相等一致,一般来说 equals() 相等,那么我们可以直接说对比的两个对象是完全相等的了。那么既然 equal() 已经能实现对比的功能了,为什么还要 hashCode() 呢?
因为重写的 equal() 里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode() 进行对比,则只要生成一个 hash 值进行比较就可以了,效率很高,那么hashCode() 既然效率这么高为什么还要 equal() 呢?
这是由于 hashCode() 并不是完全可靠的,有时候不同的对象他们生成的 hashcode 也会一样(生成 hashcode 值的公式可能存在的问题),这也对应了上面提到的结论 1。所以 hashCode() 只能说是大部分时候可靠,并不是绝对可靠。
因此,比较好的方法是:每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
这种大量的并且快速的对象对比一般使用的 hash 容器中,比如 hashset, hashmap, hashtable 等等。比如 hashset 里要求对象不能重复,则他内部必然要对添加进去的每个对象进行对比,而他的对比规则就是像上面说的那样,先 hashCode(),如果 hashCode()相同,再用 equals() 验证,如果 hashCode() 都不同,则肯定不同,这样对比的效率就很高了。
为什么要重写?
对于自定义类,默认都是继承了根父类 Object。因此,如果我们未进行方法的重写,默认调用的就是 Object 类的 hashCode() 和 equals() 方法,而 Object 类的 hashCode() 是根据当前对象的地址进行计算的,也就是说,我们 new 了两个对象,即使它们的内容是一样的(即我们认为他们逻辑上是相等的),但它们通过 hashCode() 方法计算得到的 hash 值显然是不同的,此时,我们将他们添加到 HashSet 或 HashMap 中就会出现问题。
因此,我们需要对其进行重写。比如:
public class Employee { private Integer id; private String name; private Integer age; private Double salary; @Override public int hashCode() { return Objects.hash(name, age, salary); }
这就保证了,只要两个 Employee 对象的 name、age、salary 是相等的,我们就认为它们相等。
那么,为什么要重写 equals() 方法呢?
这是由于,Object 类默认的 equals() 方法比较的是两个对象的「地址」。而大多数情况下,我们只要保证对象的某些属性是相等的,就认为它们是相等的。因此,需要对 equals() 方法进行重写。
public class Employee { private Integer id; private String name; private Integer age; private Double salary; @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) && Objects.equals(salary, employee.salary); } @Override public int hashCode() { return Objects.hash(name, age, salary); }
可通过下面的代码验证重写是否有效:
public class Main { public static void main(String[] args) { Employee e1 = new Employee(1, "zhangsan", 16, 6888.21); Employee e2 = new Employee(2, "zhangsan", 16, 6888.21); System.out.println(e1.hashCode() == e2.hashCode()); // true System.out.println(e1.equals(e2)); // true } }
为什么重写 equals() 的同时要重写 hashCode() 方法?
假设我们修改之前的 equals() 方法为:
@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(id, employee.id) && Objects.equals(name, employee.name) && Objects.equals(age, employee.age) && Objects.equals(salary, employee.salary); }
这样的后果就是,如果我们要将 Employee 对象存储到 HashSet 集合中,会发现 e1 和 e2 都成功存入了,没有达到去重的效果,而这显然是不符合我们的预期的。
public class Main { public static void main(String[] args) { Employee e1 = new Employee(1, "zhangsan", 16, 6888.21); Employee e2 = new Employee(2, "zhangsan", 16, 6888.21); HashSet<Employee> set = new HashSet<>(); set.add(e1); set.add(e2); for (Employee e : set) { System.out.println(e.toString()); } } }
输出结果为:
Employee{id=1, name='zhangsan', age=16, salary=6888.21}
Employee{id=2, name='zhangsan', age=16, salary=6888.21}