错误的hashcode/equals方法会出现什么问题?

一、HashCode简介

首先,我们看下载顶级父类Object如何解释HashCode()方法的:

image.png

我把解释单独拿出来:

image.png

总结为:

  • 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方法不相同:

image.png

这时候我们hashMap容器中才塞入了四个数据对象,然而里面的结构已经是:

  1. 断点截图table中的数据结构

image.png

image.png

  1. 图标更直观展示:

image.png

上面是在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对象放入之前的容器状态:

image.png

2、userB放入之后容器的状态:

image.png

可以看出这时候container对象的size明显是不对的,这时候table中存储的内容为:

image.png

总结:key还是之前的哪个key,但是value早就不见了

这是因为hashmap在执行put操作的时候,hashcode值定位定位数组位置;其次,在执行equals方法判断内容是否相等;

可以把上面的覆盖过程理解为发生的hash碰撞后在通过equals方法判断里面的值是不是一样的,不是追加链表后面,是就覆盖并弹出原来的旧值。

写到这里:是不是可以让你从底层存储的具体情况更加明白理解为什么引用数据类型要重写两个方法了?

四、那具体怎么重写呢

我们可以参考下jdk源码的操作方法:

image.png

在名著 《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高级架构知识点整理》

 

 由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

需要的小伙伴,可以一键三连,下方获取免费领取方式!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值