重写equals方法 和 hashcode 方法
问题:
equals和hashCode有什么作用?他们是什么关系?
在java中,所有的对象都是继承于Object类,对象比较默认调用的是Object的equals方法 和 hashcode 方法
这两个方法是用来判断对象是否相等;
equals和hashCode源码:
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
equals:
Object的equals方法的源码还是使用的==,比较两个对象的内存地址
1.对于值对象,==比较的是两个对象的值 2.对于引用对象,比较的是两个对象的地址
hashCode:
Object的hashCode方法是基于对象的ID实现的,是根据内存地址换算出来的一个值,不同的对象可能存在相等的hashcode值;
它是一个native方法,可以根据平台自行实现,但它对其实现提出了一定的要求:
- 在同样的条件下,同一个对象无论运行多少次,返回的hash值必须一样。
- 如果两个对象通过equals方法比较判定相等,则他们通过hashCode方法返回的hash值也必须相等。
- 如果两个对象通过equals方法比较判定不相等,则不必保证他们通过hashCode返回的hash值不相等。
这两个方法的作用就是为了对象间的比较,而他们之间的关系都和其方法的规则和约束有关
对于没有重写equals和hashCode时的规定如下:
如果两个对象通过equals方法比较相等,则他们hashCode的返回值一定要相等。但如果两个对象的hashCode值相等,他们通过equals方法比较的返回值则不一定相等。
如果两个对象hashCode的返回值相等,不能判断两个对象是相等的。但如果两个对象的hashCode的返回值不相等,则可以判定两个对象一定不相等。
以上所说的规则可以简单归纳为:
- 两个对象相等,hashCode一定相等
- 两个对象不等,hashCode不一定不等
- hashCode相等,两个对象不一定相等
- hashCode不等,两个对象一定不等
为什么要重写equals方法 和 hashcode 方法
在实际的业务场景中,引用对象间的比较,引用对象的内容相同,我们就认为是同一个对象,而Object的equals方法 和 hashcode 方法是通过内存地址比较的,内容相同的对象地址值可能不一样,所以为了满足实际的业务场景,需要重写equals方法 和 hashcode 方法;
- 重写前的equals方法 和 hashcode 方法基于对象的ID实现,比较的是内存地址,两个内容相等的引用对象的内存地址可能不一样,所以需要重写equals方法和 hashcode方法,根据内容判断两个引用对象是否相同;
- 重写后的equals方法 和 hashcode 方法基于对象的内容实现,比较的是对象的内容;
为什么要同时重写equals方法和hashcode方法
- 这是一个通用约定,hashCode方法的常规约定,它声明相等的对象必须有相等的哈希码;
- 相同的对象的hashCode 的散列值最好保持相等, 而不同对象的散列值,我们也使其保持不相等。重写的equals方法保证对象的内容相同,内容相等的对象hashCode方法的散列值也必须相等,这才是对象相等的结果;
重写hashCode方法提升hashmap的性能:
重写了hashCode方法后,能够确保两个对象返回的的hashCode散列值是不一样的,这样一来,
在我们使用hashmap 去存储对象, 在进行验重逻辑的时候,咱们的性能就特别好了。
hashmap在插入值时对key(这里key是对象)的验重:
HashMap中的比较key:
先求出key的hashcode(),比较其值是否相等;
-若相等再比较equals(),若相等则认为他们是相等的。
-若equals()不相等则认为他们不相等。
如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。
而HashSet,
用来判断key是否相等的方法,其实也是调用了HashMap加入元素方法,再判断是否相等。
使用list.contains()方法需重写equals():
它直接调用内部的indexof()方法去比较,可以看到内部是拿参数对象的equals()方法去比较,这个源生object对象的equals()方法, 可以看到比较的是两个对象的引用,重写equals()方法比较对象内容
场景:
我们现在需要比较两个对象 Pig 是否相等 。
而Pig 对象里面包含 三个字段, name,age,nickName ,我们现在只需要认为如果两个pig对象的name名字和age年龄一样,那么这两个pig对象就是一样的,nickName昵称不影响相等的比较判断。
public static void main(String[] args) {
Pig pig1=new Pig();
pig1.setName("A");
pig1.setAge(11);
pig1.setNickName("a");
String name= new String("A");
Pig pig2=new Pig();
pig2.setName(name);
pig2.setAge(11);
pig2.setNickName("B");
System.out.println(pig1==pig2); //false
System.out.println(pig1.equals(pig2)); //false
System.out.println(pig1.hashCode() ==pig2.hashCode()); //false
}
pig1和pig2都是新new出来的,内存地址都是不一样的。
- == : 比较内存地址 ,那肯定是false了 ;
- equals: 默认调用的是Object的equals方法,看下面源码图,显然还是使用了== ,那就还是比较内存地址,那肯定是false了;
- hashCode: 这是根据一定规则例如对象的存储地址,属性值等等映射出来的一个散列值,不同的对象存在可能相等的hashcode,但是概率非常小(两个对象equals返回true时,hashCode返回肯定是true;而两个对象hashCode返回true时,这两个对象的equals不一定返回true; 还有,如果两个对象的hashCode不一样,那么这两个对象一定不相等!)。
一个好的散列算法,我们肯定是尽可能让不同对象的hashcode也不同,相同的对象hashcode也相同。这也是为什么我们比较对象重写equals方法后还会一起重写hashcode方法。
接下来,重写equals方法和hashCode方法,实现我们这个Pig对象的比较,只要能保证name和age两个字段属性一致,就返回相等true。
重写equals方法:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pig pig = (Pig) o;
boolean nameCheck=false;
boolean ageCheck=false;
if (this.name == pig.name) {
nameCheck = true;
} else if (this.name != null && this.name.equals(pig.name)) {
nameCheck = true;
}
if (this.age == pig.age) {
ageCheck = true;
} else if (this.age != null && this.age.equals(pig.age)) {
ageCheck = true;
}
if (nameCheck && ageCheck){
return true;
}
return false;
}
重写hashCode方法:
重写hashcode方法的时候,为什么要写个31啊?
之所以使用 31, 是因为他是一个奇素数。
如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)。
使用素数的好处并不很明显,但是习惯上使用素数来计算散列结果。
31 有个很好的性能,即用移位和减法来代替乘法,
可以得到更好的性能: 31 * i == (i << 5)- i,
现代的 VM 可以自动完成这种优化。这个公式可以很简单的推导出来。
为什么还要个数字17?
这个其实道理一样,在《Effective Java》里,作者推荐使用的就是 基于17和31的散列码的算法 ,而在Objects里面的hash方法里,17换做了1 。
@Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
手动重写:
public class MenuResource {
/**
* <pre>
* 自增id
* 表字段 : menu_of_resource.id
* </pre>
*/
private Integer id;
/**
* <pre>
* 菜单id
* 表字段 : menu_of_resource.menu_id
* </pre>
*/
private String menuId;
/**
* <pre>
* 资源id
* 表字段 : menu_of_resource.resource_id
* </pre>
*/
private String resourceId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMenuId() {
return menuId;
}
public void setMenuId(String menuId) {
this.menuId = menuId == null ? null : menuId.trim();
}
public String getResourceId() {
return resourceId;
}
public void setResourceId(String resourceId) {
this.resourceId = resourceId == null ? null : resourceId.trim();
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
MenuResource other = (MenuResource) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getMenuId() == null ? other.getMenuId() == null : this.getMenuId().equals(other.getMenuId()))
&& (this.getResourceId() == null ? other.getResourceId() == null : this.getResourceId().equals(other.getResourceId()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getMenuId() == null) ? 0 : getMenuId().hashCode());
result = prime * result + ((getResourceId() == null) ? 0 : getResourceId().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", menuId=").append(menuId);
sb.append(", resourceId=").append(resourceId);
sb.append("]");
return sb.toString();
}
其他重写方式:
1.java 7 有在Objects里面新增了我们需要重新的这两个方法,所以我们重写equals和hashCode还可以使用java自带的Objects,如:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pig pig = (Pig) o;
return Objects.equals(name, pig.name) &&
Objects.equals(age, pig.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
2.lombok的注解,让它帮我们写,我们自己就写个注解
用@Data注解应该基本就够用了,它结合了@ToString,@EqualsAndHashCode,@Getter和@Setter的功能。
然后编译后,可以看到lombok帮我们重写了equals和hashcode方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?