专栏名称: 武哥聊编程
这里有技术,有段子,有生活,也有资源,要不然怎么叫 “私房菜” 呢?
目录
相关文章推荐
一念行者  ·  防意,防念,防心火 ·  18 小时前  
51好读  ›  专栏  ›  武哥聊编程

Java equals 和 hashCode 的这几个问题可以说明白吗?

武哥聊编程  · 公众号  ·  · 2020-01-10 08:40

正文

点击关注上方“ 程序员私房菜 ”,设为“置顶或星标”,第一时间送达技术干货。

前言

上一篇文章 如何妙用Spring 数据绑定机制 灵魂追问 环节留下了一个有关 equals 和 hashcode 问题 。基础面试经常会碰到与之相关的问题,这不是一个复杂的问题,但很多朋友都苦于说明他们二者的关系和约束,于是写本文做单独说明,本篇文章将循序渐进 ( 通过举例,让记忆与理解更轻松 ) 说明这些让你有些苦恼的问题,Let's go .......

面试问题

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) {
//重写逻辑
}

新买的电脑,每个电脑都有唯一的序列号,通常情况下,两个一模一样的电脑放在面前,你会说由于序列号不一样,这两个电脑不一样吗?

如果我们要说两个电脑一样,通常是比较其「品牌/尺寸/配置 」(值) ,比如这样:

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

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

2. equals 相等 和 hashcode 相等问题

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

1)两个对象 equals 相等,那他们 hashCode 相等吗?
2)两个对象 hashCode 相等,那他们 equals 相等吗?

查看 Object 类的 hashCode 方法:

public native int hashCode();

继续查看该方法的注释,明确写明关于该方法的约束

其实在这个结果的背后,还有的是关于重写 equals 方法的约束

3. 重写 equals 有哪些约束?

关于重写 equals 方法的约束,同样在该方法的注释中写的很清楚了,我在这里再说明一下:

赤橙红绿青蓝紫,七彩以色列;哆来咪发唆拉西, 一曲安哥拉 ,这些规则不是用来背诵的,只是在你需要重写 equals 方法时,打开 JDK 查看该方法,按照准则重写就好

4. 什么时候需要我们重写 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 students = new HashSet();
students.add(student1);
students.add(student2);
System.out.println("Student Set 集合长度是:" + students.size());

Map map = new HashMap();
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 方法的原因

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

2)另外通过 IDE 快捷键生成重写方法时,你也会看到这两个方法放在一起,而不是像 getter 和 setter 那样分开


以上两点都是隐形的规范约束,希望大家也严格遵守这个规范,以防带来不必要的麻烦,记忆的方式有多样,如果记不住这个文字约束,脑海中记住上面的图你也就懂了

5. 重写 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 argumentResolverCache =






请到「今天看啥」查看全文