最近我读了这篇文章 开发人员工作文件。
该文档是关于有效和正确地定义hashCode()和equals(),但我无法弄清楚为什么我们需要覆盖这两个方法。
我如何决定有效地实现这些方法?
最近我读了这篇文章 开发人员工作文件。
该文档是关于有效和正确地定义hashCode()和equals(),但我无法弄清楚为什么我们需要覆盖这两个方法。
我如何决定有效地实现这些方法?
当前回答
在下面的例子中,如果您注释掉Person类中equals或hashcode的覆盖,此代码将无法查找Tom的订单。使用哈希码的默认实现可能会导致哈希表查找失败。
下面是一个简化的代码,它按Person提取人们的订单。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);
}
}
}
其他回答
让我用非常简单的话来解释这个概念。
首先,从更广泛的角度来看,我们有集合,而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());
}
}
如果重写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
Joshua Bloch在Effective Java上说
必须在重写equals()的每个类中重写hashCode()。如果不这样做,将违反Object.hashCode()的一般约定,这将阻止您的类与所有基于哈希的集合(包括HashMap、HashSet和Hashtable)一起正常运行。
让我们通过一个例子来理解它,如果我们重写equals()而不重写hashCode()并尝试使用Map会发生什么。
假设我们有这样一个类,如果MyClass的两个对象的importantField相等(使用eclipse生成的hashCode()和equals()),则MyClass的两个对象相等
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)。所以,尽管它们是相等的,因为它们不散列到同一个桶,映射无法意识到这一点,它们都留在映射中。
虽然如果重写hashCode()就没有必要重写equals(),但让我们看看在这种特殊情况下会发生什么:我们知道MyClass的两个对象是相等的,如果它们的importantField相等,但我们没有重写equals()。
只覆盖hashCode
如果你只覆盖hashCode,那么当你调用myMap.put(first,someValue)时,它首先接受,计算它的hashCode并将其存储在给定的bucket中。然后,当您调用myMap.put(second,someOtherValue)时,它应该根据Map文档将first替换为second,因为它们是相等的(根据业务需求)。
但问题是,equals没有被重新定义,所以当map哈希second并遍历bucket时,寻找是否有一个对象k,使得second.equals(k)为真,它不会找到任何对象,因为second.equals(first)将为假。
希望这是清楚的
身份不是平等。
= operator ==测试标识符。 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方法。
不相等的实例可能具有相同的hashcode。 相同的实例应该返回相同的hashcode。
在这个回答中没有提到测试equals/hashcode契约。
我发现EqualsVerifier库非常有用和全面。它也很容易使用。
另外,从头构建equals()和hashCode()方法涉及大量样板代码。Apache Commons Lang库提供了EqualsBuilder和HashCodeBuilder类。这些类极大地简化了复杂类的equals()和hashCode()方法的实现。
顺便说一句,值得考虑重写toString()方法以帮助调试。Apache Commons Lang库提供了ToStringBuilder类来帮助实现这一点。