提问人:Shashi 提问时间:2/15/2010 最后编辑:ExploringShashi 更新时间:3/14/2023 访问量:630909
为什么我需要覆盖 Java 中的 equals 和 hashCode 方法?
Why do I need to override the equals and hashCode methods in Java?
答:
因为如果你不覆盖它们,你将使用 Object 中的默认实现。
鉴于实例相等和 hascode 值通常需要了解构成对象的内容,因此通常需要在类中重新定义它们才能具有任何有形的含义。
假设您有聚合另外两个 (B) (C) 的类 (A),并且您需要将 (A) 的实例存储在哈希表中。默认实现只允许区分实例,但不允许通过 (B) 和 (C) 进行区分。因此,A 的两个实例可能相等,但默认值不允许你以正确的方式比较它们。
它在使用值对象时很有用。以下是 Portland Pattern Repository 的摘录:
值对象的例子是事物 喜欢数字、日期、金额和 字符串。通常,它们很小 使用相当广泛的对象。 他们的身份基于他们的状态 而不是他们的对象身份。 这样,您可以拥有多个副本 相同的概念值对象。
所以我可以拥有多个副本 表示日期 1 月 16 日的对象 1998. 这些副本中的任何一个都将彼此相等。对于小 像这样的对象,它通常是 更容易创建新和移动 他们周围而不是依赖 表示日期的单个对象。
值对象应始终覆盖 Java 中的 .equals() (或 Smalltalk 中的 =)。 (记得将 .hashCode() 覆盖为 好吧。
您必须在每 覆盖 equals() 的类。失败 这样做将导致违反 总承包 Object.hashCode(),这将阻止 你的班级无法正常运行 结合所有基于哈希的 集合,包括 HashMap、 HashSet 和 Hashtable。
摘自 Joshua Bloch 的 Effective Java
通过定义和一致地定义类,可以提高类作为基于哈希的集合中的键的可用性。正如 hashCode 的 API 文档所解释的那样:“支持此方法是为了哈希表(例如由 提供的哈希表)的好处。equals()
hashCode()
java.util.Hashtable
关于如何有效地实现这些方法的问题,最好的答案是建议你阅读《有效 Java》的第 3 章。
评论
hashCode()
equals
hashCode
这两种方法都在 Object 类中定义。两者都处于最简单的实现状态。因此,当您需要时,您想为这些方法添加更多实现,然后您可以在类中覆盖。
对于 Ex: 对象中的 equals() 方法仅检查其在引用上的相等性。因此,如果您还需要比较其状态,则可以像在 String 类中一样覆盖它。
简单地说,Object 中的 equals-method 检查引用是否相等,当属性相等时,类的两个实例在语义上仍然可以相等。例如,当将对象放入使用等于和哈希码的容器(如 HashMap 和 Set)时,这一点很重要。假设我们有一个这样的类:
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
}
我们创建两个具有相同 ID 的实例:
Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");
在不覆盖等价物的情况下,我们得到:
- a.equals(b) 为 false,因为它们是两个不同的实例
- a.equals(a) 为 true,因为它是同一个实例
- b.equals(b) 为 true,因为它是同一个实例
正确?好吧,也许,如果这是你想要的。但是,假设我们希望具有相同 id 的对象是同一个对象,而不管它是否是两个不同的实例。我们覆盖等号(和哈希码):
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
@Override
public boolean equals(Object other) {
if (other instanceof Foo) {
return ((Foo)other).id.equals(this.id);
}
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}
至于实现等于和哈希码,我可以推荐使用 Guava 的辅助方法
Joshua Bloch 在《Effective Java》一书中说道
您必须在覆盖 equals() 的每个类中覆盖 hashCode()。如果不这样做,将导致违反 Object.hashCode() 的一般协定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。
让我们试着用一个例子来理解它,如果我们在不覆盖的情况下覆盖并尝试使用 .equals()
hashCode()
Map
假设我们有一个这样的类,并且两个对象相等,如果它们相等(由 eclipse 生成)MyClass
importantField
hashCode()
equals()
public class MyClass {
private final String importantField;
private final String anotherField;
public MyClass(final String equalField, final String anotherField) {
this.importantField = equalField;
this.anotherField = anotherField;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((importantField == null) ? 0 : importantField.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MyClass other = (MyClass) obj;
if (importantField == null) {
if (other.importantField != null)
return false;
} else if (!importantField.equals(other.importantField))
return false;
return true;
}
}
想象一下你有这个
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
仅覆盖等于
如果只是被覆盖,那么当你第一次调用时,它将散列到某个存储桶,当你调用它时,它将散列到其他一些存储桶(因为它们有不同的)。因此,尽管它们是相等的,但由于它们不会散列到同一个存储桶,因此地图无法实现它,并且它们都留在地图中。equals
myMap.put(first,someValue)
myMap.put(second,someOtherValue)
hashCode
虽然如果我们覆盖就没有必要覆盖,但让我们看看在这种特殊情况下会发生什么,如果我们知道两个对象相等,但它们相等,但我们不覆盖。equals()
hashCode()
MyClass
importantField
equals()
仅覆盖 hashCode
如果只覆盖,则在调用时,首先计算它并将其存储在给定的存储桶中。然后,当您调用它时,应根据地图文档将其替换为第二个,因为它们是相等的(根据业务需求)。hashCode
myMap.put(first,someValue)
hashCode
myMap.put(second,someOtherValue)
但问题是 equals 没有被重新定义,所以当 map 散列并遍历存储桶时,看看是否存在一个 true 的对象,它不会像现在这样找到任何对象。second
k
second.equals(k)
second.equals(first)
false
希望清楚
评论
if you think you need to override one, then you need to override both of them
是错误的。如果您的类覆盖但 reverse 不为 true,则需要覆盖。hashCode
equals
equals(Object)
方法,两个对象相等,那么对两个对象中的每一个调用 hashCode
方法必须产生相同的整数结果。当然,并非所有合同的所有部分都在所有代码中执行,但从形式上讲,这仍然是一种违规行为,我认为这是一个等待发生的错误。equals
Object
我正在研究解释“如果您只覆盖 hashCode,那么当您调用它时,首先计算其 hashCode 并将其存储在给定的存储桶中。然后,当您调用它时,应根据地图文档将其替换为 first,因为它们是相等的(根据我们的定义)。myMap.put(first,someValue)
myMap.put(first,someOtherValue)
我认为第二次当我们添加时,它应该是“第二个”对象,例如myMap
myMap.put(second,someOtherValue)
equals 和 hashcode 方法在对象类中定义。默认情况下,如果 equals 方法返回 true,则系统将进一步检查哈希代码的值。如果 2 个对象的哈希码也相同,则这两个对象将被视为相同。因此,如果仅覆盖 equals 方法,那么即使 overridden equals 方法指示 2 个对象相等,系统定义的哈希码也可能不指示 2 个对象相等。因此,我们也需要覆盖哈希代码。
评论
true
equals
hashCode()
:
如果只重写哈希代码方法,则不会发生任何事情,因为它始终为每个对象返回一个新的对象作为 Object 类。hashCode
equals()
:
如果仅重写 equals 方法,则为 true,则表示 a 和 b 必须相同,但这不会发生,因为您没有重写该方法。a.equals(b)
hashCode
hashCode
注意:Object 类的方法总是为每个对象返回一个新的。hashCode()
hashCode
因此,当您需要在基于哈希的集合中使用对象时,必须同时覆盖 和 。equals()
hashCode()
评论
Java 设置了一条规则
“如果使用 Object class equals 方法两个对象相等,则哈希码方法应为这两个对象提供相同的值。”
因此,如果在我们的类中我们覆盖,我们也应该覆盖方法以遵循此规则。
例如,方法和 都用于将值存储为键值对。如果我们覆盖一个而不是另一个,如果我们使用这样的对象作为键,则可能无法按我们想要的方式工作。equals()
hashcode()
equals()
hashcode()
Hashtable
Hashtable
为了使用我们自己的类对象作为 HashMap、Hashtable 等集合中的键。,我们应该通过了解收集的内部工作来覆盖这两个方法( hashCode() 和 equals() )。否则,它会导致我们意想不到的错误结果。
hashCode()
方法用于获取给定对象的唯一整数。这个整数用于确定存储桶的位置,当这个对象需要存储在一些像数据结构中时。默认情况下,Object 的方法返回存储对象的内存地址的整数表示形式。HashTable
HashMap
hashCode()
当我们将对象插入 、 或 时,会使用对象的方法。有关 Wikipedia.org 的更多信息,以供参考。hashCode()
HashTable
HashMap
HashSet
HashTables
要在映射数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义的数据类型,则键的 将确定在内部存储对象的位置。当还需要从映射中查找对象时,密钥的哈希码将确定在哪里搜索对象。hashCode()
哈希代码在内部仅指向某个“区域”(或列表、存储桶等)。由于不同的密钥对象可能具有相同的哈希代码,因此哈希代码本身并不能保证找到正确的密钥。然后迭代此区域(所有键都具有相同的哈希码),并使用键的方法找到正确的键。找到正确的键后,将返回为该键存储的对象。HashTable
equals()
因此,正如我们所看到的,在存储和查找对象时,使用了 和 方法的组合。hashCode()
equals()
HashTable
笔记:
始终使用对象的相同属性来生成和/或。在我们的案例中,我们使用了员工 ID。
hashCode()
equals()
equals()
必须一致(如果对象未修改,则它必须继续返回相同的值)。每当 时,则必须与 相同。
a.equals(b)
a.hashCode()
b.hashCode()
如果覆盖一个,则应覆盖另一个。
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
评论
hashCode()
不用于为每个对象返回一个唯一的整数。那是不可能的。你自己在第四段的第二句话中反驳了这一点。
集合(例如 和)使用对象的哈希码值来确定应如何将其存储在集合中,并再次使用哈希码来定位对象
在其收藏中。HashMap
HashSet
哈希检索过程分为两步:
- 找到合适的存储桶(使用
hashCode()
) - 在存储桶中搜索正确的元素(使用
equals()
)
这里有一个小例子,说明为什么我们应该覆盖 和 。equals()
hashcode()
考虑一个包含两个字段的类:age 和 name。Employee
public class Employee {
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Employee))
return false;
Employee employee = (Employee) obj;
return employee.getAge() == this.getAge()
&& employee.getName() == this.getName();
}
// commented
/* @Override
public int hashCode() {
int result=17;
result=31*result+age;
result=31*result+(name!=null ? name.hashCode():0);
return result;
}
*/
}
现在创建一个类,将对象插入到一个中,并测试该对象是否存在。Employee
HashSet
public class ClientTest {
public static void main(String[] args) {
Employee employee = new Employee("rajeev", 24);
Employee employee1 = new Employee("rajeev", 25);
Employee employee2 = new Employee("rajeev", 24);
HashSet<Employee> employees = new HashSet<Employee>();
employees.add(employee);
System.out.println(employees.contains(employee2));
System.out.println("employee.hashCode(): " + employee.hashCode()
+ " employee2.hashCode():" + employee2.hashCode());
}
}
它将打印以下内容:
false
employee.hashCode(): 321755204 employee2.hashCode():375890482
现在取消注释方法,执行相同的方法,输出将是:hashcode()
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
现在你能明白为什么如果两个对象被认为是相等的,它们的哈希码必须
也是平等的吗?否则,您将永远无法找到该对象,因为 Object 类中的默认哈希码方法几乎总是带有一个唯一的编号
对于每个对象,即使该方法以 2
或多个对象被视为相等。如果对象相等,则无关紧要
他们的哈希码没有反映这一点。所以再说一遍:如果两个对象相等,它们的哈希码也必须相等。equals()
评论
添加到@Lombo的答案
什么时候需要覆盖 equals() ?
Object 的 equals() 的默认实现是
public boolean equals(Object obj) {
return (this == obj);
}
这意味着只有当两个对象具有相同的内存地址时,它们才会被视为相等,而只有当您具有相同的内存地址时,它们才会被认为是相等的。 将对象与自身进行比较。
但是,如果两个对象对一个对象具有相同的值,则可能需要将两个对象视为相同 或它们的更多属性(请参阅@Lombo的答案中给出的示例)。
因此,在这些情况下,你将覆盖,你会给出你自己的平等条件。equals()
我已经成功实现了equals(),它运行良好。那么,为什么他们也要求覆盖hashCode()呢?
Well.As 只要你不在用户定义的类上使用基于“哈希”的集合,就可以了。
但是在将来的某个时候,您可能想要使用,或者如果您不“正确实现”hashCode(),这些基于哈希的集合将无法按预期工作。HashMap
HashSet
override
仅覆盖等于(对 @Lombo 的答案的补充)
myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?
首先,HashMap检查的hashCode是否与。
只有当值相同时,它才会继续检查同一存储桶中的相等性。second
first
但是这 2 个对象的 hashCode 是不同的(因为它们具有不同的内存地址 - 与默认实现不同)。 因此,它甚至不在乎检查是否相等。
如果你在被覆盖的 equals() 方法中有一个断点,如果它们有不同的 hashCode,它就不会介入。 检查,只有当它们相同时,它才会调用您的方法。contains()
hashCode()
equals()
为什么我们不能让 HashMap 检查所有存储桶中的相等性?所以我没有必要覆盖hashCode()!
那么你就错过了基于哈希的集合的意义。 请考虑以下几点:
Your hashCode() implementation : intObject%9.
以下是以存储桶形式存储的密钥。
Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...
比如说,你想知道地图是否包含键 10。 是否要搜索所有存储桶?或者您只想搜索一个存储桶?
根据 hashCode,您可以确定如果存在 10,则它必须存在于 Bucket 1 中。 所以只有 Bucket 1 会被搜索到!
让我用简单的语言来解释这个概念。
首先,从更广义的角度来看,我们有集合,并且是集合中的数据结构之一。HashMap
要理解为什么我们必须覆盖 和 方法,我们需要首先了解什么是和做什么。equals
hashCode
HashMap
A 是一种数据结构,它以数组方式存储数据的键值对。假设 a[],其中 'a' 中的每个元素都是一个键值对。HashMap
此外,上述数组中的每个索引都可以是链表,因此在一个索引处具有多个值。
现在,为什么使用?HashMap
如果我们必须在一个大数组中搜索,那么搜索每个数组都不会有效,所以哈希技术告诉我们,让我们用一些逻辑预处理数组,并根据该逻辑对元素进行分组,即哈希
例如:我们有一个数组 1,2,3,4,5,6,7,8,9,10,11,我们应用哈希函数 mod 10,因此 1,11 将被组合在一起。因此,如果我们必须在前面的数组中搜索 11,那么我们将不得不迭代完整的数组,但是当我们对其进行分组时,我们限制了迭代的范围,从而提高了速度。为简单起见,用于存储上述所有信息的数据结构可以被认为是一个二维数组。
现在,除了上述内容之外,还告诉它不会在其中添加任何重复项。这就是我们必须覆盖 和 的主要原因HashMap
equals
hashCode
所以,当说到解释内部工作时,我们需要找到它有什么方法,以及它如何遵循我上面解释的上述规则HashMap
HashMap
因此,has 方法称为 ,并根据它应遵循上述规则,即有效地分配数组并且不添加任何重复项HashMap
put(K,V)
HashMap
因此,put 的作用是,它将首先生成给定键,以决定该值应该进入哪个索引。如果该索引上不存在任何内容,则将在那里添加新值,如果那里已经存在某些内容,则应在该索引的链表结束后添加新值。但请记住,不应根据 .假设你有两个 Integer 对象 aa=11,bb=11。hashCode
HashMap
与从对象类派生的每个对象一样,比较两个对象的默认实现是它比较对象内部的引用而不是值。因此,在上述情况下,两者虽然在语义上相等,但都无法通过相等检验,并且存在两个相同值的对象的可能性,从而产生重复。如果我们覆盖,那么我们可以避免添加重复项。
您也可以参考 细节工作hashCode
import java.util.HashMap;
public class Employee {
String name;
String mobile;
public Employee(String name,String mobile) {
this.name = name;
this.mobile = mobile;
}
@Override
public int hashCode() {
System.out.println("calling hascode method of Employee");
String str = this.name;
int sum = 0;
for (int i = 0; i < str.length(); i++) {
sum = sum + str.charAt(i);
}
return sum;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
System.out.println("calling equals method of Employee");
Employee emp = (Employee) obj;
if (this.mobile.equalsIgnoreCase(emp.mobile)) {
System.out.println("returning true");
return true;
} else {
System.out.println("returning false");
return false;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Employee emp = new Employee("abc", "hhh");
Employee emp2 = new Employee("abc", "hhh");
HashMap<Employee, Employee> h = new HashMap<>();
//for (int i = 0; i < 5; i++) {
h.put(emp, emp);
h.put(emp2, emp2);
//}
System.out.println("----------------");
System.out.println("size of hashmap: "+h.size());
}
}
评论
考虑将球收集在一个桶中,全部是黑色的。你的工作是按如下方式给这些球上色,并将其用于适当的比赛,
网球 - 黄色,红色。 板球 - 白色
现在水桶有三种颜色的球:黄色、红色和白色。现在你做了着色,只有你知道哪种颜色适合哪个游戏。
给球着色 - 散列。 选择比赛用球 - 等于。
如果你做了着色,有人选择板球或网球的球,他们不会介意颜色!!
class A {
int i;
// Hashing Algorithm
if even number return 0 else return 1
// Equals Algorithm,
if i = this.i return true else false
}
- put('key','value') 将计算哈希值,用于确定
bucket 并使用 method 来查找值是否已经
存在于存储桶中。如果没有,它将被添加,否则它将被替换为当前值
hashCode()
equals()
- get('key') 将用于首先查找 Entry(存储桶)并在 Entry 中查找值
hashCode()
equals()
如果两者都被覆盖,
地图<A>
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
if equals 不被覆盖
地图<A>
Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..
如果 hashCode 未被覆盖
地图<A>
Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...
HashCode 等价合约
- 两个相等的键按照相等的方法应该生成相同的哈希码
- 生成相同哈希码的两个键不必相等(在上面的示例中,所有偶数都生成相同的哈希码)
身份不是平等。
- 等于操作员测试标识。
==
equals(Object obj)
方法比较相等性测试(即我们需要通过覆盖方法来判断相等性)
为什么我需要覆盖 Java 中的 equals 和 hashCode 方法?
首先,我们必须了解等于方法的使用。
为了识别两个对象之间的差异,我们需要覆盖 equals 方法。
例如:
Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.
------------------------------
Now I have overriden Customer class equals method as follows:
@Override
public boolean equals(Object obj) {
if (this == obj) // it checks references
return true;
if (obj == null) // checks null
return false;
if (getClass() != obj.getClass()) // both object are instances of same class or not
return false;
Customer other = (Customer) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference
return false;
return true;
}
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2); // returns true by our own logic
现在hashCode方法可以很容易地理解。
hashCode 生成整数,以便将对象存储在 HashMap、HashSet 等数据结构中。
假设我们有如上所述的 override equals 方法,Customer
customer1.equals(customer2); // returns true by our own logic
当我们将对象存储在存储桶中时,使用数据结构(存储桶是文件夹的花哨名称)。如果我们使用内置的哈希技术,对于上述两个客户,它会生成两个不同的哈希码。因此,我们将相同的对象存储在两个不同的地方。为了避免此类问题,我们应该根据以下原则重写 hashCode 方法。
- 不相等的实例可能具有相同的哈希码。
- 相等的实例应返回相同的哈希码。
Bah - “你必须在每个覆盖equals()的类中覆盖hashCode()。”
[摘自Joshua Bloch的《Effective Java?
这不是错误的方式吗?重写 hashCode 可能意味着您正在编写一个哈希键类,但重写 equals 肯定不会。有许多类不用作哈希键,但出于其他原因确实需要逻辑相等性测试方法。如果你为它选择“equals”,那么你可能会被要求通过过度应用这条规则来编写一个hashCode实现。所实现的只是在代码库中添加未经测试的代码,这是一个等待将来绊倒某人的邪恶。此外,编写不需要的代码也是反敏捷的。这是错误的(并且 ide 生成的 ide 可能与您手工制作的 equals 不兼容)。
当然,他们应该在写入的对象上强制要求一个接口用作键吗?无论如何,Object 永远不应该提供默认的 hashCode() 和 equals() 恕我直言。它可能鼓励了许多破碎的哈希集合。
但无论如何,我认为“规则”是从后到前写的。与此同时,我将继续避免使用“equals”来表示相等性测试方法:-(
在下面的示例中,如果注释掉 Person 类中等于或哈希码的重写,则此代码将无法查找 Tom 的顺序。使用哈希码的默认实现可能会导致哈希表查找失败。
我下面有一个简化的代码,可以按人拉出人们的顺序。Person 被用作哈希表中的键。
public class Person {
String name;
int age;
String socialSecurityNumber;
public Person(String name, int age, String socialSecurityNumber) {
this.name = name;
this.age = age;
this.socialSecurityNumber = socialSecurityNumber;
}
@Override
public boolean equals(Object p) {
//Person is same if social security number is same
if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
return true;
} else {
return false;
}
}
@Override
public int hashCode() { //I am using a hashing function in String.java instead of writing my own.
return socialSecurityNumber.hashCode();
}
}
public class Order {
String[] items;
public void insertOrder(String[] items)
{
this.items=items;
}
}
import java.util.Hashtable;
public class Main {
public static void main(String[] args) {
Person p1=new Person("Tom",32,"548-56-4412");
Person p2=new Person("Jerry",60,"456-74-4125");
Person p3=new Person("Sherry",38,"418-55-1235");
Order order1=new Order();
order1.insertOrder(new String[]{"mouse","car charger"});
Order order2=new Order();
order2.insertOrder(new String[]{"Multi vitamin"});
Order order3=new Order();
order3.insertOrder(new String[]{"handbag", "iPod"});
Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
hashtable.put(p1,order1);
hashtable.put(p2,order2);
hashtable.put(p3,order3);
//The line below will fail if Person class does not override hashCode()
Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
for(String item:tomOrder.items)
{
System.out.println(item);
}
}
}
Java 中的 Equals 和 Hashcode 方法
它们是 java.lang.Object 类的方法,它是所有类(自定义类以及 java API 中定义的其他类)的超类。
实现:
public boolean equals(对象对象 obj)
公共 int hashCode()
public boolean equals(对象对象 obj)
此方法仅检查两个对象引用 x 和 y 是否引用同一对象。即它检查 x 是否 == y。
它是自反的:对于任何参考值 x,x.equals(x) 都应该返回 true。
它是对称的:对于任何参考值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
它是可传递的:对于任何引用值 x、y 和 z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 应返回 true。
它是一致的:对于任何引用值 x 和 y,x.equals(y) 的多次调用始终返回 true 或始终返回 false,前提是没有修改对象的等值比较中使用的信息。
对于任何非 null 引用值 x,x.equals(null) 应返回 假。
公共 int hashCode()
此方法返回调用此方法的对象的哈希代码值。此方法以整数形式返回哈希代码值,并且支持基于哈希的集合类(如 Hashtable、HashMap、HashSet 等)。必须在重写 equals 方法的每个类中重写此方法。
hashCode的一般合约为:
每当在执行 Java 应用程序期间对同一对象多次调用它时,hashCode 方法必须始终如一地返回相同的整数,前提是没有修改对象的等值比较中使用的信息。
此整数不需要在应用程序的一次执行与同一应用程序的另一次执行之间保持一致。
如果根据 equals(Object) 方法,两个对象相等,则对两个对象中的每一个调用 hashCode 方法必须生成相同的整数结果。
根据 equals(java.lang.Object) 方法,如果两个对象不相等,则对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
相等的对象必须生成相同的哈希代码,只要它们是 相等,但不相等的对象不需要生成不同的哈希代码。
资源:
评论
恕我直言,按照规则说 - 如果两个对象相等,那么它们应该具有相同的哈希值,即相等的对象应该产生相等的哈希值。
如上所述,Object 中的默认 equals() 为 ==,它对地址进行比较,hashCode() 以整数(实际地址上的哈希值)返回地址,这对于不同的 Object 也是不同的。
如果你需要在基于 Hash 的集合中使用自定义对象,你需要覆盖 equals() 和 hashCode(),例如如果我想维护 Employee 对象的 HashSet,如果我不使用更强的 hashCode 和 equals,我最终可能会覆盖两个不同的 Employee 对象,当我使用 age 作为 hashCode() 时,就会发生这种情况, 但是,我应该使用唯一值,该值可以是员工 ID。
1)常见错误如下例所示。
public class Car {
private String color;
public Car(String color) {
this.color = color;
}
public boolean equals(Object obj) {
if(obj==null) return false;
if (!(obj instanceof Car))
return false;
if (obj == this)
return true;
return this.color.equals(((Car) obj).color);
}
public static void main(String[] args) {
Car a1 = new Car("green");
Car a2 = new Car("red");
//hashMap stores Car type and its quantity
HashMap<Car, Integer> m = new HashMap<Car, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Car("green")));
}
}
未找到绿色汽车
2. hashCode() 引起的问题
该问题是由 un-overridden 方法引起的。和之间的合同是:hashCode()
equals()
hashCode()
- 如果两个对象相等,则它们必须具有相同的哈希码。
如果两个对象具有相同的哈希代码,则它们可能相等,也可能不相等。
public int hashCode(){ return this.color.hashCode(); }
String 类和包装类的 和 方法与 Object 类的实现不同。 Object类的equals()方法比较对象的引用,而不是内容。Object 类的 hashCode() 方法为每个对象返回不同的哈希码,无论内容是否相同。equals()
hashCode()
当您使用 Map 集合并且键为 Persistent 类型、StringBuffer/builder 类型时,它会导致问题。由于它们不会覆盖 equals() 和 hashCode() 与 String 类不同,因此当您比较两个不同的对象时,equals() 将返回 false,即使它们具有相同的内容。这将使hashMap存储相同的内容密钥。存储相同的内容密钥意味着它违反了 Map 的规则,因为 Map 根本不允许重复的密钥。 因此,您可以覆盖类中的 equals() 和 hashCode() 方法并提供实现(IDE 可以生成这些方法),以便它们的工作方式与 String 的 equals() 和 hashCode() 相同,并防止相同的内容键。
您必须将 hashCode() 方法与 equals() 一起覆盖,因为 equals() 根据哈希码工作。
此外,将 hashCode() 方法与 equals() 一起覆盖有助于完整 equals()-hashCode() 合约:“如果两个对象相等,那么它们必须具有相同的哈希代码。
什么时候需要为 hashCode() 编写自定义实现?
众所周知,HashMap的内部工作是建立在Hashing原则上的。在某些存储桶中存储了条目集。您可以根据需要自定义 hashCode() 实现,以便可以将同一类别的对象存储到同一索引中。
当您使用 Method 将值存储到 Map 集合中时,put() 的内部实现为:put(k,v)
put(k, v){
hash(k);
index=hash & (n-1);
}
这意味着,它生成索引,并且索引是根据特定键对象的哈希码生成的。因此,请使用此方法根据您的要求生成哈希码,因为相同的哈希码条目集将存储到相同的存储桶或索引中。
就是这样!
为了帮助您检查重复的对象,我们需要一个自定义的 equals 和 hashCode。
由于哈希码总是返回一个数字,因此使用数字而不是字母键检索对象总是很快。它将如何做?假设我们通过传递一些在其他对象中已经可用的值来创建一个新对象。现在,新对象将返回与另一个对象相同的哈希值,因为传递的值是相同的。一旦返回相同的哈希值,JVM 每次都会转到相同的内存地址,如果同一哈希值存在多个对象,它将使用 equals() 方法来识别正确的对象。
当您想在 Map 中将自定义对象作为键进行存储和检索时,应始终覆盖自定义对象中的 equals 和 hashCode 。 例如:
Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");
这里 p1 和 p2 将被视为只有一个对象,大小将仅为 1,因为它们相等。map
public class Employee {
private int empId;
private String empName;
public Employee(int empId, String empName) {
super();
this.empId = empId;
this.empName = empName;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + "]";
}
@Override
public int hashCode() {
return empId + empName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(this instanceof Employee)) {
return false;
}
Employee emp = (Employee) obj;
return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
}
}
测试类
public class Test {
public static void main(String[] args) {
Employee emp1 = new Employee(101,"Manash");
Employee emp2 = new Employee(101,"Manash");
Employee emp3 = new Employee(103,"Ranjan");
System.out.println(emp1.hashCode());
System.out.println(emp2.hashCode());
System.out.println(emp1.equals(emp2));
System.out.println(emp1.equals(emp3));
}
}
在对象类中,equals(Object obj) 用于比较地址比较,这就是为什么在 Test 类中,如果您比较两个对象,则等于给出 false 的方法,但是当我们覆盖 hashcode() 时,它可以比较内容并给出正确的结果。
评论
如果覆盖而不是 ,则不会发现任何问题,除非您或其他人在哈希集合中使用该类类型,例如 。
在我之前的人已经多次清楚地解释了记录在案的理论,我在这里只是提供一个非常简单的例子。equals()
hashcode()
HashSet
考虑一个需要表示自定义内容的类:-equals()
public class Rishav {
private String rshv;
public Rishav(String rshv) {
this.rshv = rshv;
}
/**
* @return the rshv
*/
public String getRshv() {
return rshv;
}
/**
* @param rshv the rshv to set
*/
public void setRshv(String rshv) {
this.rshv = rshv;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Rishav) {
obj = (Rishav) obj;
if (this.rshv.equals(((Rishav) obj).getRshv())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
@Override
public int hashCode() {
return rshv.hashCode();
}
}
现在考虑这个主类:-
import java.util.HashSet;
import java.util.Set;
public class TestRishav {
public static void main(String[] args) {
Rishav rA = new Rishav("rishav");
Rishav rB = new Rishav("rishav");
System.out.println(rA.equals(rB));
System.out.println("-----------------------------------");
Set<Rishav> hashed = new HashSet<>();
hashed.add(rA);
System.out.println(hashed.contains(rB));
System.out.println("-----------------------------------");
hashed.add(rB);
System.out.println(hashed.size());
}
}
这将产生以下输出:-
true
-----------------------------------
true
-----------------------------------
1
我对结果很满意。但是,如果我没有覆盖,它将导致噩梦,因为具有相同成员内容的对象将不再被视为唯一,因为默认情况下会生成不同的行为,这是将要输出的:-hashCode()
Rishav
hashCode
true
-----------------------------------
false
-----------------------------------
2
这个答案中没有提到测试equals/hashcode合约。
我发现 EqualsVerifier 库非常有用且全面。它也非常易于使用。
此外,从头开始构建和方法涉及大量样板代码。Apache Commons Lang 库提供了 EqualsBuilder 和 HashCodeBuilder 类。这些类大大简化了复杂类的实现和方法。equals()
hashCode()
equals()
hashCode()
顺便说一句,值得考虑重写该方法以帮助调试。Apache Commons Lang 库提供了 ToStringBuilder 类来帮助解决这个问题。toString()
为什么我们覆盖 equals()
方法
在 Java 中,我们不能重载 ==、+=、-+ 等运算符的行为方式。他们以某种方式行事。因此,让我们在这里关注运算符 ==。
运算符 == 的工作原理。
它检查我们比较的 2 个引用是否指向内存中的同一实例。仅当这 2 个引用表示内存中的同一实例时,运算符才会解析为 true。==
所以现在让我们考虑下面的例子
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
因此,假设在您的程序中,您在不同的地方构建了 2 个 Person 对象,并且您希望比较它们。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
从业务角度来看,这两个对象看起来是一样的,对吧?对于 JVM,它们是不一样的。由于它们都是使用关键字创建的,因此这些实例位于内存中的不同段中。因此,运算符 == 将返回 falsenew
但是,如果我们不能覆盖 == 运算符,我们怎么能对 JVM 说我们希望将这两个对象视为相同。有方法在起作用。.equals()
您可以覆盖以检查某些对象是否具有相同的值,以便将特定字段视为相等。equals()
您可以选择要比较的字段。如果我们说 2 个 Person 对象在且仅当它们具有相同的年龄和相同的名称时才相同,那么 IDE 将创建如下所示的内容来自动生成 equals()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
让我们回到前面的例子
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
因此,我们不能重载 == 运算符来以我们想要的方式比较对象,但 Java 给了我们另一种方法,即方法,我们可以根据需要覆盖它。equals()
但是请记住,如果我们没有在类中提供自定义版本(又名覆盖),那么预定义的 Object 类和运算符的行为将完全相同。.equals()
.equals()
==
继承自 Object 的默认方法将检查两个比较的实例在内存中是否相同!equals()
为什么我们覆盖 hashCode()
方法
java 中的一些数据结构,如 HashSet、HashMap 根据应用于这些元素的哈希函数存储它们的元素。哈希函数是hashCode()
如果我们可以选择覆盖方法,那么我们也必须选择覆盖方法。这是有原因的。.equals()
hashCode()
其默认实现继承自 Object 认为内存中的所有对象都是唯一的!hashCode()
让我们回到那些哈希数据结构。这些数据结构有一个规则。
HashSet 不能包含重复值,HashMap 不能包含重复键
HashSet 是在后台使用 HashMap 实现的,其中 HashSet 的每个值都作为键存储在 HashMap 中。
因此,我们必须了解 HashMap 的工作原理。
简单来说,HashMap 是一个具有一些存储桶的原生数组。每个存储桶都有一个 linkedList。在该 linkedList 中,我们的密钥被存储。HashMap 通过应用方法为每个键找到正确的 linkedList,然后遍历该 linkedList 的所有元素,并对每个元素应用方法以检查该元素是否已包含在那里。不允许使用重复的密钥。hashCode()
equals()
当我们将某些内容放入 HashMap 中时,密钥将存储在其中一个 linkedList 中。该键将存储在哪个 linkedList 中,由该键上的方法结果显示。因此,如果结果为 4,则该 key1 将存储在数组的第 4 个存储桶中,位于该数组存在的 linkedList 中。hashCode()
key1.hashCode()
默认情况下,方法为每个不同的实例返回不同的结果。如果我们有默认的,其行为类似于 ==,它将内存中的所有实例视为不同的对象,那么我们没有任何问题。hashCode()
equals()
但在前面的示例中,我们说过,如果 Person 实例的年龄和名称匹配,我们希望它们被认为是相等的。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
现在,让我们创建一个映射,将这些实例存储为键,并将一些字符串作为对值
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
在 Person 类中,我们没有重写方法,但我们已经重写了方法。由于默认值为不同的 java 实例提供不同的结果,并且有很大的机会获得不同的结果。hashCode
equals
hashCode
person1.hashCode()
person2.hashCode()
我们的地图可能以不同 linkedList 中的那些人结束。
这违背了 HashMap 的逻辑
一个 HashMap 不允许有多个相等的键!
但是我们现在有,原因是从对象类继承的默认值是不够的。在我们重写 Person 类的方法之后,情况就不行了。hashCode()
equals()
这就是为什么我们必须在重写方法后重写方法的原因。hashCode()
equals
现在让我们解决这个问题。让我们重写我们的方法,以考虑考虑的相同字段,即hashCode()
equals()
age, name
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
现在,让我们再次尝试将这些键保存在我们的 HashMap 中
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode()
而且肯定会是一样的。假设它是 0。person2.hashCode()
HashMap 将转到存储桶 0,其中 LinkedList 会将 person1 保存为值为“1”的键。对于第二个放置,HashMap 足够智能,当它再次进入存储桶 0 以保存值为“2”的 person2 键时,它会看到那里已经存在另一个相等的键。因此,它将覆盖上一个密钥。因此,最终只有 person2 键将存在于我们的 HashMap 中。
现在我们符合 Hash Map 的规则,即不允许使用多个相等的键!
评论
..getters, setters, constructors
Objects.hash(name, age)
final
Set/Map
评论