一、HashCode简介
首先,我们看下载顶级父类Object如何解释HashCode()方法的:
我把解释单独拿出来:
总结为:
- hasdcode值具有一定的稳定性,多次调用返回结果要保持一致,并且是整数
- equals方法执行结果和hashcode值的结果一致性要保持一致
- 不是必须的操作,但是重写可以提高hash表存储的性能(减少碰撞)
二、hashcode的作用
- 假设自定义一个User对象:
package dream.on.sakura.entity;
import lombok.Data;
import lombok.ToString;
import java.util.Objects;
/** * @ClassName User * @function [业务功能] * @Author lcz * @Date 2021/07/12 11:06 */
@Data@ToString public class User {
private String name;
private String phone;
private int age;
}
hashcode/equals方法都不进行重写;
TestMain代码:
import dream.on.sakura.entity.User;
import java.util.HashMap;
/**
* @ClassName TestMain
* @function [测试程序入口]
* @Author lcz
* @Date 2021/07/12 11:06
*/
public class TestMain {
public static void main(String[] args) {
User userA = new User();
userA.setName("ABCDEa123abc");
System.out.println(userA.hashCode());
User userB = new User();
userB.setName("ABCDFB123abc");
System.out.println(userB.hashCode());
System.out.println(userA.equals(userB));
HashMap<User, User> container = new HashMap<>();
//随便放几个
User userC = new User();
userC.setName("c");
User userD = new User();
userD.setName("d");
container.put(userC, userC);
container.put(userD, userD);
container.put(userA, userA);
container.put(userB, userB);
System.out.println(container.size());
System.out.println(container.get(userA));
}
}
代码中我精心设计了一种情况,hashcode值相同但是equals方法不相同:
这时候我们hashMap容器中才塞入了四个数据对象,然而里面的结构已经是:
- 断点截图table中的数据结构
- 图标更直观展示:
上面是在debugger状态下查看了一下hashMap中的数据的存储状态,可以看出其中数据存储的紧凑程序并不理想,甚至是在四个数据情况下出现了堆压。
三、重写HashCode方法+错误的equals方法
重写hashCode方法,加上错误的equals方法或者不重写可能会导致数据覆盖;这里我为了模拟数据覆盖的情况,就重写了一个错误的equals方法:
package dream.on.sakura.entity;
import lombok.Data;
import lombok.ToString;
import java.util.Objects;
/**
* @ClassName User
* @function [业务功能]
* @Author lcz
* @Date 2021/07/12 11:06
*/
@Data
@ToString
public class User {
private String name;
private String phone;
private int age;
/*@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(name, user.name) &&
Objects.equals(phone, user.phone);
}*/
@Override
public int hashCode() {
return Objects.hash(name, phone, age);
}
/**
* 错误的hashcode方法
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return hashCode() == o.hashCode();
}
}
TestMain代码中的内容不变:
1、debugger看下代码在执行userB对象放入之前的容器状态:
2、userB放入之后容器的状态:
可以看出这时候container对象的size明显是不对的,这时候table中存储的内容为:
总结:key还是之前的哪个key,但是value早就不见了
这是因为hashmap在执行put操作的时候,hashcode值定位定位数组位置;其次,在执行equals方法判断内容是否相等;
可以把上面的覆盖过程理解为发生的hash碰撞后在通过equals方法判断里面的值是不是一样的,不是追加链表后面,是就覆盖并弹出原来的旧值。
写到这里:是不是可以让你从底层存储的具体情况更加明白理解为什么引用数据类型要重写两个方法了?
四、那具体怎么重写呢
我们可以参考下jdk源码的操作方法:
在名著 《Effective Java》第 42 页就有对 hashCode 为什么采用 31 做了说明:
之所以使用 31, 是因为他是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)。使用素数的好处并不很明显,但是习惯上使用素数来计算散列结果。 31 有个很好的性能,即用移位和减法来代替乘法,可以得到更好的性能: 31 * i == (i << 5)- i, 现代的 VM 可以自动完成这种优化。这个公式可以很简单的推导出来。
一直想整理出一份完美的面试宝典,但是时间上一直腾不开,这套一千多道面试题宝典,结合今年金三银四各种大厂面试题,以及 GitHub 上 star 数超 30K+ 的文档整理出来的,我上传以后,毫无意外的短短半个小时点赞量就达到了 13k,说实话还是有点不可思议的。
一千道互联网 Java 工程师面试题
内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)
《Java核心知识点合集(283页)》
内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等
《Java中高级核心知识点合集(524页)》
《Java高级架构知识点整理》
由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
需要的小伙伴,可以一键三连,下方获取免费领取方式!