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

1、HashCode简介

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

image.png

我把解释单独拿出来:数组

image.png

总结为:markdown

  • hasdcode值具备必定的稳定性,屡次调用返回结果要保持一致,而且是整数
  • equals方法执行结果和hashcode值的结果一致性要保持一致
  • 不是必须的操做,可是重写能够提升hash表存储的性能(减小碰撞)

2、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代码:ide

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中的数据的存储状态,能够看出其中数据存储的紧凑程序并不理想,甚至是在四个数据状况下出现了堆压。优化

3、重写HashCode方法+错误的equals方法

重写hashCode方法,加上错误的equals方法或者不重写可能会致使数据覆盖;这里我为了模拟数据覆盖的状况,就重写了一个错误的equals方法:this

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代码中的内容不变:spa

一、debugger看下代码在执行userB对象放入以前的容器状态:

image.png

二、userB放入以后容器的状态:

image.png

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

image.png

总结:key仍是以前的哪一个key,可是value早就不见了

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

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

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

4、那具体怎么重写呢

咱们能够参考下jdk源码的操做方法:

image.png

在名著 《Effective Java》第 42 页就有对 hashCode 为何采用 31 作了说明:

之因此使用 31, 是由于他是一个奇素数。若是乘数是偶数,而且乘法溢出的话,信息就会丢失,由于与2相乘等价于移位运算(低位补0)。使用素数的好处并不很明显,可是习惯上使用素数来计算散列结果。 31 有个很好的性能,即用移位和减法来代替乘法,能够获得更好的性能: 31 * i == (i << 5)- i, 现代的 VM 能够自动完成这种优化。这个公式能够很简单的推导出来。

还有一个就是上面这句话是我抄人家的!!!

相关文章
相关标签/搜索