Java equals 和 hashCode 的这几个问题能够说明白吗?

Java equals 和 hashCode 的这几个问题能够说明白吗?
前言
上一篇文章 如何妙用Spring 数据绑定机制,灵魂追问 环节留下了一个有关 equals 和 hashcode 问题 。基础面试常常会碰到与之相关的问题,这不是一个复杂的问题,但不少朋友都苦于说明他们两者的关系和约束,因而写本文作单独说明,本篇文章将按部就班 ( 经过举例,让记忆与理解更轻松 ) 说明这些让你有些苦恼的问题,Let's go .......html

面试问题java

  1. Java 里面有了 == 运算符,为何还须要 equals ?

== 比较的是对象地址,equals 比较的是对象值
先来看一看 Object 类中 equals 方法:面试

public boolean equals(Object obj) {
    return (this == obj);
}

咱们看到 equals 方法一样是经过 == 比较对象地址,并无帮咱们比较值。Java 世界中 Object 绝对是"老祖宗" 的存在,== 号咱们没办法改变或重写。但 equals 是方法,这就给了咱们重写 equals 方法的可能,让咱们实现其对值的比较:编程

@Override
public boolean equals(Object obj) {
    //重写逻辑
}

新买的电脑,每一个电脑都有惟一的序列号,一般状况下,两个如出一辙的电脑放在面前,你会说因为序列号不同,这两个电脑不同吗?ide

若是咱们要说两个电脑同样,一般是比较其「品牌/尺寸/配置 」(值) ,好比这样:测试

@Override
public boolean equals(Object obj) {
    return 品牌相等 && 尺寸相等 && 配置相等
}

当遇到如上场景时,咱们就须要重写 equals 方法。这就解释了 Java 世界为何有了 == 还有equals 这个问题了.this

  1. equals相等 和 hashcode 相等问题

关于两者,你常常会碰到下面的两个问题:3d

两个对象 equals 相等,那他们 hashCode 相等吗?
两个对象 hashCode 相等,那他们 equals 相等吗?
为了说明上面两个问题的结论,这里举一个不太恰当的例子,只为方便记忆,咱们将 equals 比做一个单词的拼写;hashCode 比做一个单词的发音,在相同语境下:code

sea / sea 「大海」,两个单词拼写同样,因此 equals 相等,他们读音 /siː/ 也同样,因此 hashCode 就相等,这就回答了第一个问题:
两个对象 equals 相等,那他们 hashCode 必定也相等htm

sea / see 「大海/看」,两个单词的读音 /siː/ 同样,显然单词是不同的,这就回答了第二个问题:
两个对象 hashCode 相等,那他们 equals 不必定相等

查看 Object 类的 hashCode 方法:

public native int hashCode();
继续查看该方法的注释,明确写明关于该方法的约束
Java equals 和 hashCode 的这几个问题能够说明白吗?
其实在这个结果的背后,还有的是关于重写 equals 方法的约束

  1. 重写 equals 有哪些约束?

关于重写 equals 方法的约束,一样在该方法的注释中写的很清楚了,我在这里再说明一下:
Java equals 和 hashCode 的这几个问题能够说明白吗?
赤橙红绿青蓝紫,七彩以色列;哆来咪发唆拉西, 一曲安哥拉 ,这些规则不是用来背诵的,只是在你须要重写 equals 方法时,打开 JDK 查看该方法,按照准则重写就好

  1. 何时须要咱们重写 hashCode?

为了比较值,咱们重写 equals 方法,那何时又须要重写 hashCode 方法呢?

一般只要咱们重写 equals 方法就要重写 hashCode 方法
为何会有这样的约束呢?按照上面讲的原则,两个对象 equals 相等,那他们的 hashCode 必定也相等。若是咱们只重写 equals 方法而不重写 hashCode 方法,看看会发生什么,举个例子来看:

定义学生类,并经过 IDE 只帮咱们生成 equals 方法:

public class Student {

    private String name;

    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }
}

编写测试代码:

Student student1 = new Student();
student1.setName("日拱一兵");
student1.setAge(18);

Student student2 = new Student();
student2.setName("日拱一兵");
student2.setAge(18);

System.out.println("student1.equals(student2)的结果是:" + student1.equals(student2));

Set<Student> students = new HashSet<Student>();
students.add(student1);
students.add(student2);
System.out.println("Student Set 集合长度是:" + students.size());

Map<Student, java.lang.String> map = new HashMap<Student, java.lang.String>();
map.put(student1, "student1");
map.put(student2, "student2");
System.out.println("Student Map 集合长度是:" + map.keySet().size());

查看运行结果:

student1.equals(student2)的结果是:true
Student Set 集合长度是:2
Student Map 集合长度是:2

很显然,按照集合 Set 和 Map 加入元素的标准来看,student1 和 student2 是两个对象,由于在调用他们的 put (Set add 方法的背后也是 HashMap 的 put)方法时, 会先判断 hash 值是否相等,这个小伙伴们打开 JDK 自行查看吧

因此咱们继续重写 Student 类的 hashCode 方法:

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

从新运行上面的测试,查看结果:

student1.equals(student2)的结果是:true
Student Set 集合长度是:1
Student Map 集合长度是:1

获得咱们预期的结果,这也就是为何一般咱们重写 equals 方法为何最好也重写 hashCode 方法的缘由

若是你在使用 Lombok,不知道你是否注意到 Lombok 只有一个 @EqualsAndHashCode 注解,而没有拆分红 @Equals 和 @HashCode 两个注解,想了解更多 Lombok 的内容,也能够查看我以前写的文章 Lomok 使用详解

另外经过 IDE 快捷键生成重写方法时,你也会看到这两个方法放在一块儿,而不是像 getter 和 setter 那样分开
Java equals 和 hashCode 的这几个问题能够说明白吗?
以上两点都是隐形的规范约束,但愿你们也严格遵照这个规范,以防带来没必要要的麻烦,记忆的方式有多样,若是记不住这个文字约束,脑海中记住上面的图你也就懂了

  1. 重写 hashCode 为何总有 31 这个数字?

细心的朋友可能注意到,我上面重写 hashCode的方法很简答, 就是用了 Objects.hash 方法,进去查看里面的方法:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

这里经过 31 来计算对象 hash 值

在 如何妙用Spring 数据绑定机制 文章末尾提到的在 HandlerMethodArgumentResolverComposite 类中有这样一个成员变量:

private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
            new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);

Map 的 key 是 MethodParameter ,根据咱们上面的分析,这个类必定也会重写 equals 和 hashCode 方法,进去查看发现,hashCode 的计算也用到了 31 这个数字

@Override
public boolean equals(Object other) {
    if (this == other) {
        return true;
    }
    if (!(other instanceof MethodParameter)) {
        return false;
    }
    MethodParameter otherParam = (MethodParameter) other;
    return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember()));
}

@Override
public int hashCode() {
    return (getMember().hashCode() * 31 + this.parameterIndex);
}

为何计算 hash 值要用到 31 这个数字呢?我在网上看到一篇不错的文章,分享给你们,做为科普,能够简单查看一下:String hashCode 方法为何选择数字31做为乘子:

(https://www.cnblogs.com/nullllun/p/8350178.html)
总结
若是还对equals 和 hashCode 关系及约束含混,咱们只须要按照上述步骤逐步回忆便可,更好的是直接查看 JDK 源码;另外拿出实际的例子来反推验证是很是好的办法。若是你还有相关疑问,也能够留言探讨.

灵魂追问

  1. Thread 类就没有重写 equals 方法,你还知道哪些状况不必重写 equals 方法吗?
  2. 从上面 HandlerMethodArgumentResolverComposite 类中定义的 Map 成员变量,你注意到哪些知识点,好比 final,ConcurrentHashMap,初识容量,为何要这样写?你能解释出缘由吗?
  • 如何妙用Spring 数据绑定机制?
  • SpringBoot如何优雅的校验参数?
  • Lombok 使用详解,简化Java编程
  • 手把手教你定制标准 Spring Boot starter

Java equals 和 hashCode 的这几个问题能够说明白吗?

Java equals 和 hashCode 的这几个问题能够说明白吗?

tan日拱一兵转发在看也很赞喜欢做者

相关文章
相关标签/搜索