equals()的作用
equals()用来判断两个对象的内存地址是否相等
Object类定义了equals,意味着所有的Java类都实现了equals()方法比较两个对象是否相等。
Object源码中,默认的equals方法等价于 ==
所以,通常会重写equals方法,比如String类库里的equals比较两个对象的内容相等。
类是否覆盖equals()方法
- 没有重写equals方法
import java.util.*;
import java.lang.Comparable;
/**
* @desc equals()的测试程序。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class EqualsTest1{
public static void main(String[] args) {
// 新建2个相同内容的Person对象,
// 再用equals比较它们是否相等
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
System.out.printf("%s\n", p1.equals(p2));
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
}
}
运行结果:
false
结果分析:我们通过 p1.equals(p2) 比较是否相等时,实际上调用的是Object.equlas()方法(即默认的 p1==p2),它是比较 p1和p2是否是同一个对象。而由p1和p2的定义可知,它们内容虽然相同,但它们是两个不同的对象,所以返回false。
- 重写equals方
import java.util.*;
import java.lang.Comparable;
/**
* @desc equals()的测试程序。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class EqualsTest2{
public static void main(String[] args) {
// 新建2个相同内容的Person对象,
// 再用equals比较它们是否相等
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
System.out.printf("%s\n", p1.equals(p2));
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}
//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
运行结果:true
结果分析:重写了Person的equals()方法后,当两个Person对象的 name 和 age 都相等,返回true
equals()和 == 的区别
== 判断两个对象的地址是否相等,及判断是否同一个对象
equals() 也是判断两个对象是否相等,但分为重写和未被重写两种情况:
1. 类没有覆盖equals方法,通过equals方法比较该类的两个对象是否相等,等价于 ==
2. 类覆盖了equals方法,如果该类的两个对象的内容相等,返回true
hashCode()的作用
hashCode()的作用是获取哈希码,也称为散列码,它实际上是返回一个int整数,用来确定该对象在哈希表中的索引位置。
hashCode()定义在JDK的Object类中,所以任何类都实现了hashCode()方法。
hashCode()在散列表中才有用,在其它情况下(例如,创建类的单个对象,或者创建类的对象数组等)没用。
在散列表中hashCode()的作用是获取对象的散列码,用来确定该对象在散列表中的位置
散列码:散列表存储的是键值对,能根据k快速检索v,这其中就用到了散列码。
散列表的本质是通过数组实现的,当我们要获取散列表中的某个v时,实际上是要获取数组中的某个位置的元素,而数组的位置,是通过k来获取,更进一步来说,数组的位置是通过k对应的散列码计算得到的。
以HashSet为例:
假设 HashSet中已经有1000个元素,当插入第1001个元素时,需要怎么处理?
HashSet是Set集合,允许有重复元素。
“将第1001个元素逐个和前面1000个元素进行比较?”,显然效率低下。
用散列表能解决这个问题,它根据元素的散列码计算出元素在散列表中的位置,然后将元素插入该位置即可。对于相同的元素,自然只保存了一个。
由此可知,若两个元素相等,它们的散列码以定向等;反之则不一定。
在散列表中:
1. 如果两个对象相等,那它们的hashCode()一定相同;
2. 如果两个对象的hashCode()相等,它们并不一定相等。
注意:这是在散列表情况中,在非散列表中一定都相等。
hashCode()和equals()的关系
从 类的用途 来区分:
- 第一种 不会创建“类对应的散列表”
这里所说的不会创建类对应的散列表是指,不会在HashSet、HashTable、HashMap等本质是散列表的数据结构中用到该类。
在这种情况下,该类的hashCode()和equals()各不相关
在这种情况下,equals()用来比较该类的两个对象是否相等,而hashCode()毫无作用。
package com.example.demo.ctrl;
import lombok.Data;
import java.util.Objects;
@Data
public class Person {
int age;
String name;
/**
* 覆盖equals方法
*/
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
//如果是同一个对象返回true,反之返回false
if (this == o) {
return true;
}
//判断是否类型相同
if(this.getClass() != o.getClass()){
return false;
}
Person person = (Person)o;
return name.equals(person.name) && age==person.age;
}
}
在重写对象的equlas()方法使得判断对象内容是否一致的情况下,如果两个对象相等,对象各自的hashCode()也不一定相等
- 第二种 创建了 “类对应的散列表”
这里所说的“会创建类对应的散列表”是指,会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。
在这种情况下,该类的 hashCode()和equals() 是有关系的:
- 如果两个对象相等,它们的hashCode()值一定相同。
这里的相等是指通过equals()比较两个对象时返回true - 如果两个对象的hashCode()相等,它们并不一定相等。
因为在散列表中,hashCode()相等,即两个kv对的哈希值相等,然后哈希值相等并不一定能得出键值对相等。
在这种情况下,如果要判断两个对象是否相等,equals()和hashCode()都要覆盖重写。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。
如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。
import java.util.*;
import java.lang.Comparable;
/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest1{
public static void main(String[] args) {
// 新建Person对象,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
// 新建HashSet对象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);
// 比较p1 和 p2, 并打印它们的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "("+name + ", " +age+")";
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}
//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
运行结果:
p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]
结果分析:
只重写了equals(),但是类创建了对应的散列表却未重写hashCode(),所以HashSet里仍然有重复元素。
虽然p1和p2内容相等,但是它们的hashCode()不同,所以HashSet在添加p1和p2的时候认为他们不相等。
下面同时覆盖equals()和hashCode()
import java.util.*;
import java.lang.Comparable;
/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest2{
public static void main(String[] args) {
// 新建Person对象,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
Person p4 = new Person("EEE", 100);
// 新建HashSet对象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);
// 比较p1 和 p2, 并打印它们的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 比较p1 和 p4, 并打印它们的hashCode()
System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " - " +age;
}
/**
* @desc重写hashCode
*/
@Override
public int hashCode(){
int nameHash = name.toUpperCase().hashCode();
return nameHash ^ age;
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}
//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
运行结果:
p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]
结果分析:
重写hashCode()和equals()后,HashSet能正确去重
比较p1和p2,它们的hashCode()相等,通过equals()比较它们也返回true,所以p1和p2被视为相等。
比较p1和p4,虽然它们的hashCode()相等,但是通过equals()比较返回false,所以p1和p4被视为不相等。