hashCode()方法和equals()方法的作用其实一样,都是用来比较两个对象是否相等,既然equals()方法已经能实现对比的功能,为什么还要用hashCode()呢?
首先我们来看Object类
java.lang.Object类中有两个非常重要的方法:
public int hashCode();
public boolean equals(Object obj);
Object类是所有类的父类,所有的对象,包括数组,都实现了在Object类中定义的方法
equals()方法详解
equals()方法使用来判断当前对象和其它对象是否相等
源码
public boolean equals(Object obj) {
return (this == obj);
}
很明显是对两个对象的地址进行比较(引用是否相同)。但是,String、和包装类在使用equals()方法时,已经覆盖了
Object类的equals()方法。
为什么重写equals()方法时必须重写hashCode()方法
我们用一个案例演示一下,
1.新建一个Person 类中没有重写equals()方法和hashCode()方法
public class Person {
private int age;
private String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
测试类
package com.blog.controller;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 18);
Person p2 = new Person("张三", 18);
System.out.println("equals()比较:"+p1.equals(p2));
System.out.println("p1---hashCode:"+p1.hashCode());
System.out.println("p2---hashCode:"+p2.hashCode());
Map<Person, String> map = new HashMap<>();
map.put(p1, "zhangsan");
System.out.println("结果是:" + map.get(p2));
}
}
输出结果:
equals()比较:false
p1---hashCode:1995265320
p2---hashCode:746292446
结果是:null
执行main方法发现,运行equals()方法的执行结果为false;
分析:
Pserson类没有覆盖equals()方法,p1调用的equals()方法实际上等同于调用Object类的equals()方法。比较的是对象的内存地址是否相等。因为两个新对象所以内存地址不同,则p1.equals(p2)
2.只重写hashCode()方法而不重写equals()方法时
public class Person {
private int age;
private String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}
输出结果:
equals()比较:false
p1---hashCode:24021577
p2---hashCode:24021577
结果是:null
分析:
hash码是age和name生成的,我们没有重写equals方法只重写了hashCode()方法,两个对象的hashCode值一样,但通过map.get(p2)获取值却null,因为在hashMap.get()方法中会进行equals()方法进行比较两个对象是否为false,所以就没有比较两个对象的hashCode值
##3.重写equals()方法不重写hashCode()方法时,让Person类通过判断对象内容是否相等类确定对象是否相等
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person)o;
return age == person.age && Objects.equals(name, person.name);
}
}
输出结果:
equals()比较:true
p1---hashCode:1995265320
p2---hashCode:746292446
结果是:null
分析:
因为Person两个对象的age和name属性相等,而且重写了equals方法来判断的,所以p1.equals(p2)为true,注意 :map.get(p2)时,期望结果是"zhangsan"却为null
为什么map.get(p2)时,期望结果是"zhangsan"为null
用equals比较对象相同,但是在hashMap中却以不同的对象存储(没有重写hashCode(),两个hashCode()值,在它看来就是两个对象)
到底两个对象相等不相等?
说明必须重写hashCode()的重要性
为什么会出现这种与自己期望不一样的结果呢
Person类中没有重写hashCode()方法,导致了两个相同的实例具有不相同的散列码(hashCode),违背了hashCode约定。
##4.重写equals()和hashCode()
public class Person {
private int age;
private String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person)o;
return age == person.age && Objects.equals(name, person.name);
}
@Override public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}
输出结果:
equals()比较:true
p1---hashCode:24021577 //相同的值
p2---hashCode:24021577
结果是:zhangsan //说明以一个值key存储,相同的值
当map.get(p2)时,结果是"zhangsan"
这也就说明了重写equals()方法同时重写hashCode()方法,就是为了保证当这两个对象equals()方法比较相等时,那么hashCode值也一定要相等,equals为true,hashCode相等,所以在map.get(p2)时,结果是"zhangsan"
##5.那么我们就来做一个特别的例子吧,用hashSet添加对象,然后修改一下p2的属性值
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 18);
Person p2 = new Person("张三", 18);
System.out.println("equals()比较:"+p1.equals(p2));
System.out.println("p1---hashCode:"+p1.hashCode());
System.out.println("p2---hashCode:"+p2.hashCode());
Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("set.size():"+set.size());
p2.setAge(20);
System.out.println("set.remove:"+set.remove(p2));
}
}
输出结果:
equals()比较:true
p1---hashCode:24021577
p2---hashCode:24021577
set.size():1
set.remove:false
分析:
在获取集合元素对象放入set中时,那么以后该对象的属性参与了hashCode的计算,伴儿以后就不能修改该对象参与的hashCode计算的那些属性了,否在可能会引起一些意想不到的错误
HashSet的add
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
map.put源码
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
##通过案例回到上面问题:equals()方法已经能实现对比的功能,为什么还要用hashCode()呢?
-
重写的equals方法里一般比较的比 全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只需要生成hash值进行比较就可以了,效率也很高,既然hashCode效率这么高,为哈还要重写equals()呢。
-
因为我们的hashCode()并不是完全可靠,有时候不同的对象生成的hashCode也会一样,所以说hashCode()方法大部分时候可靠,并不是绝对可靠,由此我们可以得出:
- equals()相等对象他们的hashCode肯定相等,也就是equals比较是绝对可靠的
- hashCode()相等的两个对象他们的equals()不一定相等,也就是为什么说hashCode不是绝对可靠的。
- hashCode 只要在集合中用的到
总结
1.为什么要重写equals呢?因为在java的集合中,通过equals来判断两个对象是否相等的
2.当集合元素过多时,或者是重写equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率较低,所以引入hashCode这个方法,为了提高效率
我们也可以得出:
- equals相等的两个对象他们的hashCode肯定是相等的,也就是equals去比较对象是绝对可靠的
- hashCode相等的对象他们呢的equals不一定相等,这也是hashCode不是绝对可靠的。
- 换句话说equals不相等的对象hashCode有可能相等(可能是哈希码产生造成的冲突)