简介
1、保证单一原则:equals相同的两个对象的hashcode必须相同。如果重写了equals而没有重写hashcode,会出现equals相同hashcode不相同这个现象。
2、在无序集合中(如Set),使用hashcode来计算key应存储在hash表的索引,如果重写了equals而没有重写hashcode,会出现两个完全相同的对象。因为hashcode不同,计算出的索引不同,那么这些集合就会混乱。
3、提高效率,当比较两个对象是否相同时,先比较hashcode是否相同,如果hashcode不相同肯定不是一个对象,如果hashcode相同再调用equals来进行比较,减少了比较次数从而提高效率。
数据存储或读取的方式
1、普通方式
2、hashCode存储或读取的方式
从上图可以明显看出,hashCode读写数据的效率明显高于普通方式。因此当我们对比两个对象是否相等时,我们就可以先使用 hashCode 进行比较,如果比较的结果是 true,那么就可以使用 equals 再次确认两个对象是否相等,如果比较的结果也是 true,那么这两个对象就是相等的,否则就认为两个对象不相等。这样就大大的提升了比较的效率,这也是为什么开发时使用 hashCode 和 equals 协同的方式来进行确认两个对象是否相等的原因。
那为什么不直接使用 hashCode 确定两个对象是否相等呢?这是因为不同对象的 hashCode 可能相同,但 hashCode 不同的对象一定不相等,所以使用 hashCode 可以起到快速初次判断对象是否相等的作用从而提高效率。
equals()方法
Object 类中的 equals ()方法用于检测一个对象是否等于另外一个对象。在 Object 类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。
public boolean equals(Object obj) {
return (this == obj);
}
但是大多数情况直接通过equals ()的判断是没有什么意义的,参照如下实例
public class Test {
public static void main(String[] args) {
Person u1 = new Person();
u1.setName("您好");
u1.setAge(18);
Person u2 = new Person();
u1.setName("您好");
u1.setAge(18);
//打印equals()结果
System.out.println("两个对象是否相同:" + u1.equals(u2));
}
}
class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果如下
不难看出即使两个对象完全相等但是返回值依然是false,因此判断两个对象是否相等一定要重写equals()方法。
hashCode()方法
hashCode 代表散列码,它是由对象推导出的一个整型值,并且这个值为任意整数,包括正数或负数。需要注意的是:散列码是没有规律的。如果 x 和 y 是两个不同的对象,x.hashCode() 与 y.hashCode() 基本上不会相同。但如果 a 和 b 相等,则 a.hashCode() 一定等于 b.hashCode()。
public native int hashCode();
从上述源码可以看到,Object 类中的 hashCode 调用了一个(native)本地方法,返回了一个 int 类型的整数(可能是正数也可能是负数)。
那么hashCode相等代表两个对象一定相等吗?请看下述实例
public class Test {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = "您好";
String str1 = "Aa";
String str2 = "BB";
System.out.println("s1 hashCode:" + s1.hashCode());
System.out.println("s2 hashCode:" + s2.hashCode());
System.out.println("s3 hashCode:" + s3.hashCode());
System.out.println("str1 hashCode:" + str1.hashCode());
System.out.println("str2 hashCode:" + str2.hashCode());
}
}
输出结果如下
不难看出即使两个对象不相等,它们的hashCode也有可能相同,因此判断两个对象是否相等同样必须对hashCode()方法进行重写。
为什么equals()方法和hashCode()方法必须一起重写
Set集合的特点没有重复的元素,因此我们以它为例来阐述为何equals()方法和hashCode()方法必须一起重写。
public class Test {
public static void main(String[] args) {
HashSet<String> has = new HashSet<String>();
has.add("李白");
has.add("王维");
has.add("李白");
has.add("杜甫");
has.add("杜甫");
has.add("白居易");
has.add("苏轼");
System.out.println(has);
}
}
输出结果如下
可以看到,重复的元素并没有成功添加到集合中。那么,如果我们只重写equals()方法会发生什么呢?
public class Test {
public static void main(String[] args) {
// 对象 1
Person p1 = new Person();
p1.setName("您好");
p1.setAge(18);
// 对象 2
Person p2 = new Person();
p2.setName("您好");
p2.setAge(18);
// 创建 Set 集合
Set<Person> set = new HashSet<Person>();
set.add(p1);
set.add(p2);
// 打印 Set 中的所有数据
set.forEach(p -> {
System.out.println(p);
});
}
}
class Person {
private String name;
private int age;
// 只重写了 equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true; // 引用相等返回 true
// 如果等于 null,或者对象类型不同返回 false
if (o == null || getClass() != o.getClass()) return false;
// 强转为自定义 Person 类型
Person person = (Person) o;
// 如果 age 和 name 都相等,就返回 true
return age == person.age &&
Objects.equals(name, person.name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出结果如下
不难看出,即使两个对象是相等的,Set 集合也没有将二者进行去重与合并。这就是重写了 equals ()方法但没有重写 hashCode ()方法的问题所在。
下面我们对hashCode ()方法也进行重写
public class Test {
public static void main(String[] args) {
// 对象 1
Person p1 = new Person();
p1.setName("您好");
p1.setAge(18);
// 对象 2
Person p2 = new Person();
p2.setName("您好");
p2.setAge(18);
// 创建 Set 集合
Set<Person> set = new HashSet<Person>();
set.add(p1);
set.add(p2);
// 打印 Set 中的所有数据
set.forEach(p -> {
System.out.println(p);
});
}
}
class Person {
private String name;
private int age;
// 重写了 equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true; // 引用相等返回 true
// 如果等于 null,或者对象类型不同返回 false
if (o == null || getClass() != o.getClass()) return false;
// 强转为自定义 Person 类型
Person person = (Person) o;
// 如果 age 和 name 都相等,就返回 true
return age == person.age &&
Objects.equals(name, person.name);
}
// 重写了 hashCode 方法
@Override
public int hashCode() {
// 对比 name 和 age 是否相等
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出结果如下
可以看出,如果只重写了 equals ()方法,那么Set 集合进行去重操作时,会先判断两个对象的 hashCode 是否相同,此时因为没有重写 hashCode 方法,所以会直接执行 Object 类中的 hashCode() 方法,而 Object 类中的 hashCode ()方法对比的是两个不同引用地址的对象,所以结果是 false,那么 equals ()方法就不用执行了,直接返回的结果就是 false:两个对象不是相等的,于是就在 Set 集合中插入了两个相同的对象。
但是,如果在重写 equals ()方法时,也重写了 hashCode ()方法,那么在执行判断时会去执行重写的 hashCode ()方法,此时对比的是两个对象的所有属性的 hashCode 是否相同,于是调用 hashCode 返回的结果就是 true,再去调用 equals ()方法,发现两个对象确实是相等的,于是就返回 true ,因此 Set 集合就不会存储两个一模一样的数据了。
综上所述,当我们同时重写了两个方法之后,Set 集合成功进行了去重的操作,代表此时两个对象是完全相等的。