Object 类有equals方法和hashCode方法, 定义如下:
boolean java.lang.Object.equals(Object obj) //注意参数变量类型是Object.
public native int hashCode();
对象等同,通常指==相等。
如果对象存在“逻辑相等”,而且超类没有重写(override) equals方法以实现逻辑相等,则需要重写equals方法和hashCode方法。
Java Object 规范要求, 如果重写了equals方法,必须同时重写hashCode方法,否则可能无法查询到存储在散列集合中的对象。
两个对象equals相等,那么hashCode一定相同,反之则不成立。
如何重写equals方法和hashCode方法呢?
重写equals方法:
1. 使用==操作符检查 “参数是否为这个对象的引用”;
2. 使用instanceof检查“参数是否是正确的类型”;
3. 把参数转换成正确的类型;
4. “关键”域比较
示例: 员工类Employee 有多个域,存在这样的“逻辑相等”:
如果两个Employee 对象的“关键”域employeeId和name都相同,则这两个Employee对象相同。
public class Employee {
private int employeeId;
private String name;
private Date onboardDate;
private String title;
public Employee(int employeeId, String name) {
this.employeeId = employeeId;
this.name = name;
}
public int getEmployeeId() {
return employeeId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getOnboardDate() {
return onboardDate;
}
public void setOnboardDate(Date onboardDate) {
this.onboardDate = onboardDate;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(!(obj instanceof Employee)) return false;
Employee employee = (Employee)obj;
return this.employeeId == employee.employeeId &&
(this.name == employee.name ||
(this.name != null && this.name.equals(employee.name))
);
}
//need to override hasCode()
}
重写hashCode方法:
散列码的定义影响存储在散列集合中的对象的查询效率。
一个好的散列方法通常为不相等的对象产生不相等的散列码。
为每个“关键”域计算散列码,即为equals方法中涉及的域计算散列码,然后再合并。
公式: result = 31 * result + c;
c 为每个域计算的散列码:
1. boolean类型的域,则计算(f ? 1 : 0);
2. byte, char, short, int类型的域, 则计算(int)f;
3. long类型的域,则计算(int)(f^(f>>>32));
4. float类型的域,则计算Float.floatToIntBits(f);
5. double类型的域,则计算Double.doubleToLongBits(f), 然后再按照long类型值计算散列值;
6. 对象引用类型的域,则调用这个对象的散列函数
7. 数组类型的域,则调用Arrays.hashCode
示例:以上面的Employee类为例,employeeId 和 name是关键域。
@Override
public int hashCode() {
int result = 17; //一个非0的常量值作为初始值
result = 31 * result + employeeId;
result = 31 * result + name.hashCode();
return result;
}
散列集合对象通过散列码查找对象,如果一个对象的散列码发生改变,有可能在散列集合中找不到该对象。
public class Test {
public static void main(String[] args) {
Employee e = new Employee(1, "Sunny");
List<Employee> list = new ArrayList<>();
list.add(e);
Set<Employee> set = new HashSet<>();
set.add(e);
e.setName("Tom");//change name field, then hash code will be changed
System.out.println(list.contains(e)); //print true, it is expected;
System.out.println(set.contains(e)); //print false, the object is not found due to hash code changed
set.remove(e);
System.out.println(set.size()); //print 1, not able to be removed due to hash code changed
set.add(e);
System.out.println(set.size()); //print 2, not replace the same object due to hash code changed
}
}
HashSet是用HashMap来存储对象,键(key)是对象本身;
HashSet中存储的对象,对象的“关键”域(equals方法中的域)应该是不可变的 或 事实不可变的.
HashMap的键(key)如果是对象类型,此键对象的“关键”域(equals方法中的域)应该是不可变的 或 事实不可变的.