介绍:
Java Object类提供了方法的基本实现– hashCode()和equals()。 这些方法非常有用,尤其是在使用Collection框架时。 哈希表实现依赖于这些方法来存储和检索数据。
在本教程中,我们将学习hashCode()和equals()之间的协定(它们的默认实现)。 我们还将讨论何时以及如何覆盖这些方法。
默认行为:
首先让我们看一下这些方法的默认实现:
存在于Object类中的equals()方法只是比较对象引用:
public boolean equals(Object obj) {return (this == obj);
}
因此,默认情况下, obj1.equals(obj2)与obj1 == obj2相同。
equals()方法比较诸如String等的类的实际值,因为它们在相应的类中被覆盖。
JDK中hashCode()方法的签名为:
public native int hashCode();
在这里, native关键字表示该方法是使用JNI (Java本机接口)以本机代码实现的。
hashCode()方法返回一个int类型。 默认情况下,返回值表示对象存储器地址。
实施原则:
在覆盖equals()和hashCode()方法之前,我们先来看一下准则:
1. equals(): 我们对equals()方法的实现必须是:
- 反身:对于任何参考值obj , obj.equals(obj)应该返回true
- 对称:对于参考值obj1和obj2 ,如果obj1.equals(obj2)为true,则obj2.equals(obj2)也应返回true
- 传递性:对于值OBJ1参考,OBJ 2和 OBJ 3,如果obj1.equals(OBJ 2) 真实 ,obj2.equals(OBJ 3)为真 ,那么obj1.equals(OBJ 3)也应该返回true
- 一致:只要我们没有更改实现, equals()方法的多次调用必须始终返回相同的值
2. hashCode():实现hashCode()时,必须考虑以下几点:
- 在一次执行中, hashCode()的多次调用必须返回相同的值,前提是我们不更改equals()实现中的属性
- 相等的对象必须返回相同的hashCode()值
- 两个或更多不相等的对象可以具有相同的hashCode()值
尽管在覆盖这些方法时要牢记上述所有原则,但是其中有一个流行的规则:
对于两个对象obj1和obj2 ,
- 如果obj1.equals(obj2),则obj1.hashCode()= obj2.hashCode()必须为true
- 但是,如果obj1.hashCode()== obj2.hashCode() ,则obj1.equals(obj2)可以返回true或false,即obj1和obj2可能相等或不相等
这通常被称为equals()和hashCode()契约。
为什么覆盖
hashCode()和equals()方法在基于哈希表的实现中存储和检索元素方面起着重要作用。 hashCode()确定给定项映射到的存储桶。 在存储桶中, equals()方法用于查找给定的条目。
假设我们有一个Employee类:
public class Employee {private int id;private String name;//constructors, getters, setters, toString implementations}
还有一个存储Employee作为键的HashMap :
Map<Employee, Integer> map = new HashMap<>();map.put(new Employee(1, "Sam"), 1);
map.put(new Employee(2, "Sierra"), 2);
现在我们已经插入了两个条目,让我们尝试一个containsKey()检查:
boolean containsSam = map.containsKey(new Employee(1, "Sam")); //false
尽管我们有Sam的条目,但是containsKey()返回false 。 这是因为我们尚未覆盖equals()和hashCode()方法。 默认情况下, equals()只会进行基于引用的比较。
覆盖
根据Javadocs:
当我们覆盖equals()方法时,我们还必须覆盖hashCode()方法。
这将有助于避免违反equals-hashCode合同。
请注意,如果我们违反合同,编译器不会抱怨,但是当我们将此类对象作为键存储在HashMap中时,最终可能会遇到意外行为。
我们可以使用IDE的功能快速覆盖这些方法。 使用Eclipse时,我们可以转到Source-> Generate hashCode()和equals()。 让我们看看为Employee类生成的实现:
public class Employee {...@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + id;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Employee other = (Employee) obj;if (id != other.id)return false;if (name == null) {if (other.name != null)return false;} else if(!name.equals(other.name))return false;return true;}
}
显然,相同的字段已用于实现equals()和hashCode()方法,以与合同保持一致。
最佳做法:
使用equals()和hashCode()时应遵循的一些最佳实践包括:
- 实现hashCode()以在各个存储桶之间平均分配项目。 这样做的目的是最大程度地减少碰撞次数,从而获得良好的性能
- 对于equals()和hashCode()实现,我们应该使用相同的字段
- 在HashMap中将不可变对象作为键,因为它们支持缓存哈希码值
- 使用ORM工具时,请始终使用getter代替hashCode()和equals()方法定义中的字段。 那是因为有些字段可能是延迟加载的
结论:
在本教程中,我们首先研究了equals()和hashCode()方法的默认实现。 稍后,我们讨论了何时以及如何覆盖这些方法。
成为第一个发表评论的人。
翻译自: https://www.javacodegeeks.com/2019/05/java-equals-hashcode.html