在这个Java Challenger中,您将学习equals()
和hashcode()
结合如何使Java程序中的对象比较高效而又容易。 简而言之,这些方法可以共同验证两个对象是否具有相同的值。
如果没有equals()
和hashcode()
我们将不得不创建非常大的“ if
”比较,以比较对象的每个字段。 这会使代码真正混乱并且难以阅读。 这两种方法共同帮助我们创建了更灵活,更紧密的代码。
在Java中重写equals()和hashcode()
方法重写是一种技术,其中为了利用多态性 ,在子类中再次编写(重写)了父类或接口的行为。 Java中的每个Object
都包含一个equals()
和hashcode()
方法,但是必须重写它们才能正常工作。
为了了解覆盖如何与equals()
和hashcode()
,我们可以研究它们在核心Java类中的实现。 下面是Object
类中的equals()
方法。 该方法正在检查当前实例是否与先前传递的Object
相同。
public boolean equals(Object obj) {
return (this == obj);
}
如果未覆盖hashcode()
方法,则将调用Object
类中的默认方法。 这是一个本地方法 ,这意味着它将以另一种语言(如C)执行,并将返回有关该对象的内存地址的一些代码。 (除非您正在编写JDK代码,否则确切地知道此方法的工作原理并不重要。)
@HotSpotIntrinsicCandidate
public native int hashCode();
当不覆盖equals()
和hashcode()
方法时,您会看到上述方法被调用。 在这种情况下,这些方法不能满足equals()
和hashcode()
的实际目的,即检查两个或多个对象是否具有相同的值。
通常,当覆盖equals()
还必须覆盖hashcode()
。
用equals()比较对象
我们使用equals()
方法比较Java中的对象。 为了确定两个对象是否相同, equals()
比较这些对象的属性值:
public class EqualsAndHashCodeExample {
public static void main(String... equalsExplanation) {
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) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Simpson simpson = (Simpson) o;
return age == simpson.age &&
weight == simpson.weight &&
name.equals(simpson.name);
}
}
}
在第一次比较中, equals()
将当前对象实例与已传递的对象进行比较。 如果两个对象的值相同,则equals()
将返回true
。
在第二个比较中, equals()
检查传递的对象是否为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()与==
乍看之下, ==
运算符和equals()
方法似乎可以完成相同的操作,但实际上它们的工作方式不同。 ==
运算符比较两个对象引用是否指向同一对象。 例如:
System.out.println(homer == homer2);
在第一次比较中,我们使用new
运算符实例化了两个不同的Simpson
实例。 因此,变量homer
和homer2
将指向内存堆中的不同Object
引用。 因此,结果将为false
。
System.out.println(homer.equals(homer2));
在第二个比较中,我们重写equals()
方法。 在这种情况下,仅会比较名称。 由于两个Simpson
对象的名称均为“ Homer”,因此结果为true
。
用hashcode()唯一标识对象
比较对象时,我们使用hashcode()
方法来优化性能。 执行hashcode()
将为程序中的每个对象返回唯一的ID,这使比较对象的整个状态的任务变得更加容易。
如果一个对象的哈希码与另一个对象的哈希码不同,则没有理由执行equals()
方法:您只知道两个对象是不同的。 在另一方面,如果哈希码是相同的,那么你必须执行equals()
方法来确定的值和字段是否相同。
这是一个带有hashcode()
的实际示例。
public class HashcodeConcept {
public static void main(String... hashcodeExample) {
Simpson homer = new Simpson(1, "Homer");
Simpson bart = new Simpson(2, "Homer");
boolean isHashcodeEquals = homer.hashCode() == bart.hashCode();
if (isHashcodeEquals) {
System.out.println("Should compare with equals method too.");
} else {
System.out.println("Should not compare with equals method because " +
"the id is different, that means the objects are not equals for sure.");
}
}
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
接口的类:
Set
只能插入唯一的元素,因此,例如,如果要将元素添加到HashSet
类中,则必须首先使用equals()
和hashcode()
方法来验证该元素是否唯一。 如果在这种情况下未覆盖equals()
和hashcode()
方法,则可能会在代码中插入重复的元素。
在下面的代码中,我们使用add
方法向HashSet
对象添加一个新元素。 在添加新元素之前, HashSet
检查该元素是否已存在于给定的集合中:
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
如果对象相同,则不会插入新元素。
杂凑集
Set
不是唯一使用equals()
和hashcode()
集合。 HashMap , Hashtable和LinkedHashMap也需要这些方法。 通常,如果看到前缀为“哈希”的集合,则可以确定它需要重写hashcode()
和equals()
方法以使其功能正常工作。
使用equals()和hashcode()的准则
您应该仅对具有相同唯一哈希码ID的对象执行equals()
方法。 当哈希码ID 不同时,不应执行equals()
。
表1.哈希码比较
如果hashcode() 比较... | 然后 … |
---|---|
返回true | 执行equals() |
返回假 | 不执行equals() |
由于性能原因,此原理主要用于Set
或Hash
集合中。
对象比较规则
当hashcode()
比较返回false
, equals()
方法还必须返回false 。 如果哈希码不同,则对象肯定不相等。
表2.使用hashcode()进行对象比较
当哈希码比较返回时... | equals() 方法应返回... |
---|---|
真正 | 对或错 |
假 | 假 |
当equals()
方法返回true
,表示对象的所有值和属性都相等。 在这种情况下,哈希码比较也必须为true。
表3.使用equals()进行对象比较
当equals() 方法返回时... | hashcode() 方法应返回... |
---|---|
真正 | 真正 |
假 | 对或错 |
接受equals()和hashcode()挑战!
现在该使用equals()
和hashcode()
方法测试您的技能了。 您在此挑战中的目标是找出两个equals()
方法比较的输出并猜测Set
集合的大小。
首先,请仔细研究以下代码:
public class EqualsHashCodeChallenge {
public static void main(String... doYourBest) {
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()
方法为两个对象返回相同的值。
在第二个equals()
方法比较中, hashcode()
方法被overridenHomer
变量overridenHomer
。 名称是“荷马”两个Simpson
对象,但是hashcode()
方法返回一个不同的值overriddenHomer
。 在这种情况下, equals()
方法的最终结果将为false
因为该方法包含与哈希码的比较。
您可能会注意到,集合的大小设置为可容纳三个Simpson
对象。 让我们详细检查一下。
集合中的第一个对象将被正常插入:
new Simpson("Homer");
同样,下一个对象也将正常插入,因为它具有与上一个对象不同的值:
new Simpson("Marge");
最后,以下Simpson
对象具有与第一个对象相同的值。 在这种情况下,将不会插入对象:
set.add(new Simpson("Homer"));
众所周知, overridenHomer
对象使用与常规Simpson(“Homer”)
实例化不同的哈希码值。 因此,此元素将被插入到集合中:
overriddenHomer;
答案键
这个Java挑战者的答案是B。 输出为:
true
false
3
视频挑战! 调试equals()和hashcode()
调试是完全吸收编程概念并改善代码的最简单方法之一。 在此视频中,您可以在调试和解释Java equals()
和hashcode()
挑战的同时进行后续操作。
翻译自: https://www.infoworld.com/article/3305792/comparing-java-objects-with-equals-and-hashcode.html