最近我读了这篇文章 开发人员工作文件。
该文档是关于有效和正确地定义hashCode()和equals(),但我无法弄清楚为什么我们需要覆盖这两个方法。
我如何决定有效地实现这些方法?
最近我读了这篇文章 开发人员工作文件。
该文档是关于有效和正确地定义hashCode()和equals(),但我无法弄清楚为什么我们需要覆盖这两个方法。
我如何决定有效地实现这些方法?
当前回答
Java提出了一个规则
如果使用Object类的equals方法,两个对象相等,那么hashcode方法应该为这两个对象提供相同的值。
因此,如果在我们的类中重写equals(),我们也应该重写hashcode()方法来遵循此规则。 例如,equals()和hashcode()这两个方法都用于Hashtable中以键值对的形式存储值。如果我们覆盖其中一个而不是另一个,如果我们使用这样的对象作为键,哈希表可能不会像我们想要的那样工作。
其他回答
Java提出了一个规则
如果使用Object类的equals方法,两个对象相等,那么hashcode方法应该为这两个对象提供相同的值。
因此,如果在我们的类中重写equals(),我们也应该重写hashcode()方法来遵循此规则。 例如,equals()和hashcode()这两个方法都用于Hashtable中以键值对的形式存储值。如果我们覆盖其中一个而不是另一个,如果我们使用这样的对象作为键,哈希表可能不会像我们想要的那样工作。
简单地说,Object中的equals-方法检查引用是否相等,当属性相等时,类的两个实例在语义上仍然相等。例如,当将对象放入使用等号和hashcode(如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)为假,因为它们是两个不同的实例 a.equals(a)为真,因为它是同一个实例 b.equals(b)为真,因为它是同一个实例
正确吗?也许吧,如果这是你想要的。但假设我们希望具有相同id的对象是相同的对象,不管它是否是两个不同的实例。我们重写等号(和hashcode):
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();
}
}
至于实现equals和hashcode,我建议使用Guava的helper方法
让我用非常简单的话来解释这个概念。
首先,从更广泛的角度来看,我们有集合,而hashmap是集合中的数据结构之一。
要理解为什么我们必须重写equals和hashcode方法,如果需要的话,首先要理解什么是hashmap以及它的功能。
hashmap是一种以数组方式存储键值对数据的数据结构。假设是a[],其中'a'中的每个元素都是一个键值对。
此外,上述数组中的每个索引都可以是链表,因此在一个索引上有多个值。
为什么要使用hashmap呢?
如果我们必须在一个大数组中搜索,那么搜索每个数组,如果它们不是有效的,那么哈希技术告诉我们,让我们用一些逻辑预处理数组,并根据该逻辑对元素进行分组,即哈希
例如:我们有数组1、2、3、4、5、6、7、8、9、10、11,我们应用哈希函数mod 10,所以1、11将被分组在一起。因此,如果我们必须在前一个数组中搜索11,那么我们必须迭代整个数组,但当我们对它进行分组时,我们限制了迭代的范围,从而提高了速度。为了简单起见,用于存储所有上述信息的数据结构可以看作是一个2d数组
现在除了上面的hashmap还告诉它不会在其中添加任何duplicate。这就是为什么我们要重写等号和hashcode的主要原因
因此,当我们说要解释hashmap的内部工作时,我们需要找到hashmap有什么方法,以及它如何遵循上面我解释过的规则
所以hashmap有一个方法叫as put(K,V),根据hashmap,它应该遵循上面的规则,有效地分配数组,不添加任何重复
put所做的是首先为给定的键生成hashcode来决定值应该放在哪个索引中。如果那个下标处什么都没有,那么新值就会被加到那里,如果那里已经有了,那么新值就会被加到链表末尾那个下标处。但是请记住,不应该根据期望的hashmap行为添加重复项。假设你有两个整数对象aa=11 bb=11。
由于每个对象都派生自对象类,比较两个对象的默认实现是比较引用,而不是对象内部的值。因此,在上述情况下,尽管语义上相同,但两个对象都将无法通过相等性测试,并且有可能存在两个具有相同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());
}
}
加上@Lombo的答案
什么时候需要重写equals() ?
Object的equals()的默认实现是
public boolean equals(Object obj) {
return (this == obj);
}
这意味着两个对象将被认为是相等的,只有当他们有相同的内存地址,这将是真的,只有当你是 比较对象本身。
但是,如果两个对象对一个对象具有相同的值,则可能认为它们是相同的 或更多的属性(参考@Lombo的回答中给出的例子)。
所以在这些情况下,你会重写equals()你会给出你自己的相等条件。
我已经成功地实现了equals(),它工作得很好。那么为什么他们要求重写hashCode()呢?
好。只要不在用户定义的类上使用基于“Hash”的集合,就没问题。 但是在将来的某个时候,你可能想要使用HashMap或HashSet,如果你没有覆盖和“正确实现”hashCode(),这些基于Hash的集合将无法正常工作。
只覆盖等于(除了@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检查second的hashCode是否与First相同。 只有当值相同时,它才会继续检查同一桶中的相等性。
但这里这两个对象的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中。 所以只有桶1会被搜索!!
hashCode ():
如果只重写hash-code方法,什么也不会发生,因为它总是为每个对象返回一个新的hashCode作为object类。
equals ():
如果你只覆盖equals方法,如果a.equals(b)为真,这意味着a和b的hashCode必须是相同的,但这不会发生,因为你没有覆盖hashCode方法。
注意:Object类的hashCode()方法总是为每个对象返回一个新的hashCode。
因此,当您需要在基于哈希的集合中使用对象时,必须重写equals()和hashCode()。