Java Challengers#4:将对象与equals()和hashCode()进行比较
预期将在“ Java Developer”课程上发布新线程,我们将继续翻译一系列Java Challengers文章,其前面的部分可以在下面的链接中阅读:
走吧
在本文中,您将学习equals()
和hashCode()
方法之间的关系以及在比较对象时如何使用它们。

在不使用equals()
和hashCode()
来比较两个对象的状态的情况下,我们需要编写大量的“ if
”比较来比较对象的每个字段。 这种方法使代码混乱并且难以阅读。 这两种方法一起工作,有助于创建更灵活,更一致的代码。
本文的源代码在这里 。
覆盖equals()和hashCode()
方法重写是一种技术,通过该技术可以在子类中重写(重新定义)父类或接口的行为(请参见Java Challengers#3:多态与继承 , 英文 )。 在Java中,每个对象都具有equals()
和hashCode()
方法,并且它们必须被重写才能正常工作。
为了了解equals()
和hashCode()
的重新定义是如何工作的,让我们检查一下Java基类中的实现。 以下是Object
类的equals()
方法。 该方法检查当前实例是否与传递的obj
对象匹配。
public boolean equals(Object obj) { return (this == obj); }
现在让我们看一下Object
类中的hashCode()
方法。
@HotSpotIntrinsicCandidate public native int hashCode();
这是本机的-用另一种语言(例如C)编写的方法,它返回一些与对象的内存地址关联的数字代码。 (如果您不编写JDK代码,则确切了解此方法的工作原理并不重要。)
译者注:与地址关联的值并不完全正确( 感谢 vladimir_dolzhenko )。 默认情况下,HotSpot JVM使用伪随机数。 HotSpot的hashCode()实现的描述在此处和此处 。
如果不覆盖equals()
和hashCode()
方法,则将改为调用上述Object
类的方法。 在这种情况下,这些方法不能满足equals()
和hashCode()
的实际目的,即检查对象是否具有相同的状态。
通常,覆盖equals()
也会覆盖hashCode()
。
用equals()比较对象
equals()
方法用于比较对象。 要确定对象是否相同, equals()
比较对象字段值:
public class EqualsAndHashCodeExample { public static void main(String... args){ System.out.println(new Simpson("Homer", 35, 120) .equals(new Simpson("Homer",35,120))); System.out.println(new Simpson("Bart", 10, 120) .equals(new Simpson("El Barto", 10, 45))); System.out.println(new Simpson("Lisa", 54, 60) .equals(new Object())); } static class Simpson { private String name; private int age; private int weight; public Simpson(String name, int age, int weight) { this.name = name; this.age = age; this.weight = weight; } @Override public boolean equals(Object o) {
让我们看一下equals()
方法。 第一个比较将当前实例与this
传递的o
进行比较。 如果是同一对象,则equals()
将返回true
。
第二个比较检查以查看传递的对象是否为null
以及它是什么类型。 如果传输的对象是其他类型,则对象不相等。
最后, equals()
比较对象的字段。 如果两个对象具有相同的字段值,则这些对象是相同的。
分析对象比较的选项
现在让我们看一下main()
方法中用于比较对象的选项。 首先,我们比较两个Simpson
对象:
System.out.println( new Simpson("Homer", 35, 120).equals( new Simpson("Homer", 35, 120)));
这些对象的字段具有相同的值,因此结果为true
。
然后再次比较两个Simpson
对象:
System.out.println( new Simpson("Bart", 10, 45).equals( new Simpson("El Barto", 10, 45)));
这里的对象相似,但名称的含义不同: Bart和El Barto 。 因此,结果将为false
。
最后,让我们比较Simpson
对象和Object
类的实例:
System.out.println( new Simpson("Lisa", 54, 60).equals( new Object()));
在这种情况下,结果将为false
,因为对象的类型不同。
等于()与==
乍一看,似乎==
运算符和equals()
方法执行相同的操作,但是实际上它们的工作方式不同。 ==
运算符比较两个链接是否指向同一对象。 例如:
Simpson homer = new Simpson("Homer", 35, 120); Simpson homer2 = new Simpson("Homer", 35, 120); System.out.println(homer == homer2);
我们使用new
运算符创建了Simpson
两个不同实例。 因此,变量homer
和homer2
将指向堆中的不同对象。 因此,结果是false
。
在下面的示例中,我们使用重写的equals()
方法:
System.out.println(homer.equals(homer2));
在这种情况下,将比较这些字段。 由于两个Simpson
对象都具有相同的字段值,因此结果为true
。
用hashCode()标识对象
为了在比较对象时优化性能,使用了hashCode()
方法。 hashCode()
方法为每个对象返回唯一的标识符,从而简化了对象状态的比较。
如果一个对象的哈希码与另一个对象的哈希码不匹配,则可以省略equals()
方法:您只知道两个对象不匹配。 另一方面,如果哈希码相同,则需要执行equals()
方法来确定字段值是否匹配。
考虑一个带有hashCode()
的实际示例。
public class HashcodeConcept { public static void main(String... args) { Simpson homer = new Simpson(1, "Homer"); Simpson bart = new Simpson(2, "Homer"); boolean isHashcodeEquals = homer.hashCode() == bart.hashCode(); if (isHashcodeEquals) { System.out.println(" equals."); } else { System.out.println(" equals, .. " + " , , ."); } } static class Simpson { int id; String name; public Simpson(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Simpson simpson = (Simpson) o; return id == simpson.id && name.equals(simpson.name); } @Override public int hashCode() { return id; } } }
始终返回相同值的hashCode()
方法有效,但效率不高。 在这种情况下,比较将始终返回true
,因此将始终执行equals()
方法。 在这种情况下,无法提高性能。
对集合使用equals()和hashCode()
实现Set
接口(集合)的类应防止添加重复的元素。 以下是一些实现Set
接口的类:
只能将唯一元素添加到Set
。 因此,例如,如果要将元素添加到HashSet
,则必须首先使用equals()
和hashCode()
方法来确保此元素是唯一的。 如果equals()
和hashCode()
方法未被覆盖,则可能会插入重复的值。
让我们看一下HashSet
add()
方法的实现部分:
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e;
在添加新项目之前, HashSet
检查该项目是否存在于此集合中。 如果对象匹配,则不会插入新元素。
不仅在Set
中使用equals()
和hashCode()
方法。 HashMap , Hashtable和LinkedHashMap也需要这些方法。 通常,如果看到带有“ Hash”前缀的集合,则可以确保对于其正确操作,需要覆盖hashCode()
和equals()
方法。
有关使用equals()和hashCode()的建议
仅对具有相同哈希码的对象运行equals()
方法。 如果哈希码不同,则不要执行 equals()
。
表1.哈希码比较
如果hashCode()比较... | 那... |
---|
返回true | 执行equals() |
返回false | 不执行equals() |
由于性能原因,该原理主要用于Set
或Hash
集合中。
对象比较规则
当hashCode()
比较返回false
, equals()
方法也应该返回 false
。 如果哈希码不同,则对象肯定不相等。
表2.具有hashCode()的对象的比较
当hashCode() 比较返回... | equals() 方法应返回... |
---|
true | true 还是false |
false | false |
当equals()
方法返回true
,这意味着对象的所有值和属性都相等。 在这种情况下,哈希码比较也应该为真。
表3.具有equals()的对象的比较
当equals() 方法返回时... | hashCode() 方法应返回... |
---|
true | true |
false | true 还是false |
解决equals()和hashCode()上的问题
现在该测试您对equals()
和hashCode()
方法的了解了。 任务是找出几个equals()
的结果以及Set
集合的总大小。
首先,请仔细研究以下代码:
public class EqualsHashCodeChallenge { public static void main(String... args) { System.out.println(new Simpson("Bart").equals(new Simpson("Bart"))); Simpson overriddenHomer = new Simpson("Homer") { public int hashCode() { return (43 + 777) + 1; } }; System.out.println(new Simpson("Homer").equals(overriddenHomer)); Set set = new HashSet(Set.of(new Simpson("Homer"), new Simpson("Marge"))); set.add(new Simpson("Homer")); set.add(overriddenHomer); System.out.println(set.size()); } static class Simpson { String name; Simpson(String name) { this.name = name; } @Override public boolean equals(Object obj) { Simpson otherSimpson = (Simpson) obj; return this.name.equals(otherSimpson.name) && this.hashCode() == otherSimpson.hashCode(); } @Override public int hashCode() { return (43 + 777); } } }
首先,分析代码,考虑结果是什么。 然后才运行代码。 目的是提高代码分析技能并学习基本的Java概念,以便使您的代码更好。
结果是什么?
A) true true 4 B) true false 3 C) true false 2 D) false true 3
发生什么事了 了解equals()和hashCode()
在第一个比较中, equals()
的结果为true
,因为对象的状态相同,并且hashCode()
方法为两个对象返回相同的值。
在第二个比较中, hashCode()
方法被重写为hashCode()
变量。 对于两个Simpson
对象,名称均为“ Homer” ,但对于overriddenHomer
hashCode()
方法将返回不同的值。 在这种情况下, equals()
方法的结果将为false
,因为它包含与哈希码的比较。
您必须已经意识到集合中将存在三个Simpson
对象。 让我们弄清楚。
集合中的第一个对象将照常插入:
new Simpson("Homer");
以下对象也将以通常的方式插入,因为它包含与前一个对象不同的值:
new Simpson("Marge");
最后,下一个Simpson
对象具有与第一个对象相同的名称值。 在这种情况下,将不会插入对象:
set.add(new Simpson("Homer"));
众所周知,与常规Simpson("Homer")
实例Simpson("Homer")
不同, overridenHomer
对象使用不同的哈希值。 因此,该项目将被插入到集合中:
set.add(overriddenHomer);
答案
正确答案是B。结论将是:
true false 3
equals()和hashCode()的常见错误
- 缺少覆盖的
hashCode()
以及覆盖的equals()
,反之亦然。 - 使用
HashSet
类的哈希集合时,缺少覆盖equals()
和hashCode()
。 - 在
hashCode()
方法中返回常量值,而不是为每个对象返回唯一的代码。 - 等价使用
==
和equals()
。 ==
运算符比较对象引用,而equals()
方法比较对象值。
关于equals()和hashCode()的注意事项
- 建议始终在POJO( 俄语 , 英语 )中覆盖
equals()
和hashCode()
方法。 - 使用高效的算法来创建唯一的哈希码。
- 覆盖
equals()
方法时,请始终覆盖hashCode()
方法。 equals()
方法应比较对象的完整状态(来自字段的值)。hashCode()
方法可以是POJO标识符(ID)。- 如果比较两个对象的哈希码的结果为
false
,则equals()
方法也必须为false
。 - 如果在使用哈希集合时未重新定义
equals()
和hashCode()
,则该集合将具有重复的元素。
了解有关Java的更多信息
传统上,我会等待您的评论并邀请您参加公开课程 ,该课程将于3月18日由我们的老师Sergei Petrelevich举办