最近我读了这篇文章 开发人员工作文件。
该文档是关于有效和正确地定义hashCode()和equals(),但我无法弄清楚为什么我们需要覆盖这两个方法。
我如何决定有效地实现这些方法?
最近我读了这篇文章 开发人员工作文件。
该文档是关于有效和正确地定义hashCode()和equals(),但我无法弄清楚为什么我们需要覆盖这两个方法。
我如何决定有效地实现这些方法?
当前回答
如果重写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
其他回答
hashCode()方法用于获取给定对象的唯一整数。这个整数用于确定桶的位置,当这个对象需要存储在一些HashTable, HashMap之类的数据结构时。默认情况下,Object的hashCode()方法返回存储Object的内存地址的整数表示形式。
对象的hashCode()方法用于将对象插入到HashTable、HashMap或HashSet中。更多关于哈希表的信息请参见Wikipedia.org。
要在map数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义的数据类型,则键的hashCode()将确定在内部将对象存储在何处。当还需要从映射中查找对象时,键的哈希码将确定在哪里搜索对象。
哈希码只在内部指向某个“区域”(或列表,桶等)。因为不同的键对象可能具有相同的哈希码,所以哈希码本身并不能保证找到正确的键。然后哈希表迭代该区域(具有相同哈希代码的所有键),并使用键的equals()方法找到正确的键。一旦找到正确的键,就会返回为该键存储的对象。
因此,正如我们所看到的,hashCode()和equals()方法的组合在哈希表中存储和查找对象时使用。
注:
总是使用对象的相同属性来生成hashCode()和equals()。在我们的例子中,我们使用了员工id。 Equals()必须是一致的(如果对象没有被修改,那么它必须一直返回相同的值)。 当a. = (b)时,则a.hashCode()必须与b. hashcode()相同。 如果你覆盖了一个,那么你也应该覆盖另一个。
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
HashMap和HashSet等集合使用对象的hashcode值来确定该对象应该如何存储在集合中,然后再次使用hashcode来定位该对象 在它的收藏中。
哈希检索是一个两步过程:
找到正确的桶(使用hashCode()) 在桶中搜索正确的元素(使用equals())
下面是一个关于为什么我们应该重写equals()和hashcode()的小例子。
考虑一个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
现在uncomment hashcode()方法,执行相同的方法,输出将是:
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
Now can you see why if two objects are considered equal, their hashcodes must also be equal? Otherwise, you'd never be able to find the object since the default hashcode method in class Object virtually always comes up with a unique number for each object, even if the equals() method is overridden in such a way that two or more objects are considered equal. It doesn't matter how equal the objects are if their hashcodes don't reflect that. So one more time: If two objects are equal, their hashcodes must be equal as well.
加上@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会被搜索!!
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')将使用hashCode()计算哈希值来确定 桶,并使用equals()方法查找该值是否已经 出现在桶里。如果不是,它将被添加,否则它将被替换为当前值 get('key')将使用hashCode()首先找到条目(桶) equals()来查找Entry中的值
如果两者都被覆盖,
地图<A>
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
If =没有被覆盖
地图<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等价契约
根据equal方法,两个相等的键应该生成相同的hashCode 生成相同hashCode的两个key不需要相等(在上面的例子中,所有偶数生成相同的hashCode)
它在使用值对象时很有用。以下摘自Portland Pattern Repository:
Examples of value objects are things like numbers, dates, monies and strings. Usually, they are small objects which are used quite widely. Their identity is based on their state rather than on their object identity. This way, you can have multiple copies of the same conceptual value object. So I can have multiple copies of an object that represents the date 16 Jan 1998. Any of these copies will be equal to each other. For a small object such as this, it is often easier to create new ones and move them around rather than rely on a single object to represent the date. A value object should always override .equals() in Java (or = in Smalltalk). (Remember to override .hashCode() as well.)