再也不怕面试问为什么要重写 hashcode 和 equals 方法了
. 1. HashMap的高效性
在我们常用的数据结构中链表结构,举个例子ArrayList,假如ArrayList里面有n个元素那么它的平均算法的时间消耗就要n/2,n的基数越大,消耗的时长就越大,这个无疑是一种弊端,所以出现了hasp算法。
在HaspMap中是通过hasp算法来进行存储的,每个存在HashMap的元素的存储位置都与hash算法直接挂钩,它在获取元素的时候不用每个每个的一次遍历,而是直接通过生成元素的hash值的方式来找到对应的元素的存储位置,所以算法上是非常高效的。
举个例子吧,假如我的hash函数是x*10%,这里只是举一个例子,真正的hash函数是没那么简单的,这里只是为了简单说明问题。假如我有一个10要存进去,那么计算出来的hash值为1就作为索引下标,然后将值存进对应的位置,然后我又有一个30的值,通过hash计算出来的hash值就是3作为索引下标,将30存进对一个的位置,假如我要取10值,那么将对应的10值传进来,计算出来的hash值就是1,直接对应索引就能找到对应的值,这样的效率就非常高了。
但是hash算法也是有弊端的,会出现“hash冲突问题”,例如在存10和11的时候都会计算出hash值位1,在HaspMap中是利用“链表+红黑树”的方案来决解hasp冲突的。如下图所示。
它具体的实现是,当一个值进行存储的时候,先进行hash值得计算,通过hash值遭到对应的存储位置,然后判断该位置上是否已经存在有值,若是有值,然后进行equals方法得比较,若是equals放比比较位false,新的值就会挂载在旧的值得后面形成一种链表结构,当链表得节点数大于8的时候就会自动转化为红黑树得结构,因为这是考虑到了表的节点数非常多的时候,查询效率就会非常低。
. 2. 重写equals和hashCode方法的原因
上面讲了那么多例子,那么为什么要重写equals和hashCode方法呢,其实在我们用HashMap存储我们自定义对象的时候就要重写equals和hashCode方法,假如不重写得话当我们用HashMap得api操作里面得存储对象的时候,就有可能出现和我们预期的结果不一样的现象,下面来举个例子。
import java.util.HashMap;
class User{
private Integer id;
private Stirng name;
public User(Integer id, String name) {
this.id = id;
this.name=name;
}
public Integer getId() {
return id;
}
public Integer getName() {
return name;
}
// @Override
// public int hashCode() {
// final int prime = 31;
// int result = 1;
// result = prime * result + ((id == null) ? 0 : id.hashCode());
// result = prime * result + ((name == null) ? 0 : name.hashCode());
// return result;
// }
//
// @Override
// public boolean equals(Object obj) {
// if (this == obj)
// return true;
// if (obj == null)
// return false;
// if (getClass() != obj.getClass())
// return false;
// User other = (User) obj;
// if (id == null) {
// if (other.id != null)
// return false;
// } else if (!id.equals(other.id))
// return false;
// if (name == null) {
// if (other.name != null)
// return false;
// } else if (!name.equals(other.name))
// return false;
// return true;
// }
}
public class TestHashCode {
public static void main(String[] args) {
User user1= new User(1,”zhangsan”);
User user2= new User(1,”zhangsan”);
HashMap<User, String> userMap= new HashMap<User, String>();
userMap.put(user1, "我是第一个用户");
System.out.println(userMap.get(user2));
}
}
在mian方法里面定义了两个对象user1和user2,但是user里面的属性对象是一样的,接着创建一个HashMap叫做userMap,将user1存进去,然后用user2来来取值,取出来的值为null。
这里的原因很简单,因为没有重写hash和equals方法,当我们用user2来取直的时候,就会用user2的地址值生成一个hash值,因为user1和user2是完全两个对象所以生成的hash肯定是不同的。
举个例子,效果如下图所示。
当我们把user1存进去的时候,根据地址值生成的索引是1010,user1就存放在对应的位置,接着我们用user2来取值的时候,根据user2来生成的地址值是3030的索引值所对应的位置,所以就返回为null了。
由于user1和user2是完全不同的对象,两份地址值是不一样的,因为没有重写hashCode方法,就会沿用父类Object中的hashCode的方法来生成Hash值,而Object中的hashCode方法生成的hash值是根据对象的地址值直接生成的,所以两个对象的hash就会完全不一样。
当我们把hashCode方法的注释去点后,但是equals的方法不去掉,也进行测试,返回的结果还是为空,这又是为什么呢?原因很简单,因为当我们把user1存进去的时候,hash值是根据user1中的属性生成hash值,因为user1和user2有相同的属性值,所以生成的hash值是一样的,当用user2来取值同样也能找到user1,但是此时还会进行equals方法的比较,而由于没有进行重写equals,所以使用的是Object的equals的方法进行的比较,而Object中的equals方法是直接使用==来比较,实际还是比较的是对象的地址值,这样比较肯定user1不等于user2啦,所以就返回空,这个就是HashMap的完整的取值过程。
这也就是为什么要重写hashCode和equals方法了,在HashMap中存储了自定义对象后,要进行取值的过程,会会进行hashCode和equals方法的比较,假如不重写,那么使用的就是Object中的hashCode和equals中的方法,都是利用地址值来比较,这样就会出问题了,我们更期望的是,当来两个不同的对象,但是他们的属性值是完全一样的,我们认为这是同一个东西,这个才是我们真正期望的。
. 3. 面试问题
其实面试官在问你为什么要重写hashcode或者equals方法的时候,可能是想考你HashMap的底层原理,在我们的项目中经常会用到HashMap,在后台返回前端数据的时候基本都是用HashMap来封装的,大家也知道在面试的时HashMap是百分之90的概率都会问到的,但是直接问你HaspMap有什么特性,这未免太没有新意了吧,这不就换个说法喽。
更多的教程请关注:非科班的科班,我就是我,一个一直在努力前行的码农