1. hashCode()简介
该方法主要是利用一定的规则生成对象的哈希码,也称散列码。它是是由对象导出的一个整数值,是没有规律的。
关于hashCode()使用的哈希算法,越糟糕的哈希算法越容易产生哈希碰撞(产生重复的哈希码),不过这也与数据值域分布特性有关。
2. hashCode()的作用
在将对象插入类似于HashSet等集合中时,
- 首先计算对象的hashCode,来判断对象加入的位置,同时也会与其他已经加入的对象的hashCode值作比较。
- 如果没有相等的hashCode,HashSet会假设对象没有重复出现,直接将其加入;
- 但是如果发现有重复的hashCode值的对象,会调用equals方法来检查hashCode值相等的对象是否真的相同。
- 如果不同,就会重新散列到不同的位置;如果相等,则不进行插入。
3. 既生equals,何生hashCode
可以看出hashCode和equals方法,本质都是用来比较对象是否相等的。
为什么JDK要同时提供两个方法呢?
如上所述,在一些容器,比如HashSet、HashMap中,hashCode方法的存在是非常有必要的,它可以减少使用equals比较的次数。
毕竟,在较少数据的情况下,使用equals进行比较,结果会很准确,也很方便。
但是,如果数据量很大呢?
此时使用equals一条一条按照规则比较两个对象是否相等的复杂度,是不可估量的。而这正是hashCode的用途所在,它产生的哈希码是个整型数据,比较起来相对于对象更加简单。很显然,有hashCode大大提高了比较的效率。
为什么重写了equals后,就也要重写hashCode呢?
在实际应用中,都是按照一定规则来判断对象是否相等的,比如说工号相等的两个员工,可以认为是同一个人。这时,为了使程序判定二者相等,势必要重写equals。
那么,hashCode呢?
不重写时
比较时,使用的是Object中的hashCode方法。
结果大概率会是,重写后的equals判断相等的两个对象,原始的hashCode方法获取的哈希码是不相等的。
情况一
当然,如果不将对象放入散列表中,其实问题不大,因为直接equals比较两个对象就可以了,hashCode比较的结果,根本无所谓啦。
情况二
那么当想要将两个对象插入进HashSet呢?
问题来啦!
首先调用hashCode判断,二者不相等,那么,插入成功。也就是说,此时,员工表中存在两个工号相同的员工。
那就现实意义而言,一张员工表中,可以存在两个工号相同的员工嘛?很显然,不能。因为这在现实中,就是员工信息出现重复了。
这也就是重写equal后,要重写hashCode的原因。
文字叙述不够直观,直接来栗子吧。
4. 栗子1:
重写了equals(),没有重写hashCode()。
注意啊,这里的重写,是要加**@Override**的。不然,Objects可是不承认的。
1. 员工类
package test;
/**
* @author LISHANSHAN
* @ClassName Employee
* @Description TODO
* @date 2022/04/2022/4/22 16:19
*/
public class Employee {
private String employeeId;
private String employeeName;
private double salary;
public Employee(String employeeId, String employeeName, double salary) {
this.employeeId = employeeId;
this.employeeName = employeeName;
this.salary = salary;
}
@Override
public boolean equals(Object o) {
Employee employee = (Employee) o;
if (employeeId.equals(employee.getEmployeeId())) {
return true;
} else {
return false;
}
}
@Override
public String toString() {
return "Employee{" +
"employeeId='" + employeeId + '\'' +
", employeeName='" + employeeName + '\'' +
", salary=" + salary +
'}';
}
public String getEmployeeId() {
return employeeId;
}
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
2. 测试方法
package test;
import java.util.HashSet;
import java.util.Set;
/**
* @author LISHANSHAN
* @ClassName TestEquals
* @Description TODO
* @date 2022/04/2022/4/22 16:24
*/
public class TestEquals {
public static void main(String[] args) {
Employee e1 = new Employee("001", "张三", 10000);
Employee e2 = new Employee("001", "张三", 10000);
System.out.println("e1的hashcode: " + e1.hashCode());
System.out.println("e2的hashcode: " + e2.hashCode());
System.out.println("e1和e2相等? " + e1.equals(e2));
System.out.println("e1和e2的哈希码相等?" + (e1.hashCode() == e2.hashCode()));
HashSet<Employee> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());
set.forEach((employee)-> System.out.println(employee));
}
}
3. 运行结果
5. 栗子2:
如何重写
简单介绍两种重写的方式吧。
- 最好使用null安全的Objects.hashCode()。如果其参数为null,会返回0,否则返回对参数调用hashCode方法的结果。
另外,对double类型的对象,调用静态方法Double.hashCode方法来避免创建Double对象。系数是随便取的。 - 需要组合多个散列值时,可以调用Objects.hash并提供多个参数,会对各个参数调用Objects.hashCode,并组合这些散列值。
@Override
public int hashCode() {
return 7 * Objects.hashCode(employeeId)
+ 11 * Double.hashCode(salary)
+ 13 * Objects.hashCode(employeeName);
}
@Override
public int hashCode() {
return Objects.hash(name, salary, hireDay);
}
增加一个hashCode()
还是上述例子,Employee类中新增一个hashCode方法,如下:
@Override
public int hashCode() {
return Objects.hash(employeeId, employeeName, salary);
}
其余代码不变。
运行结果:
很显然,判重成功。