最近,我通读了这份 Developer Works文档。
该文档主要是关于定义hashCode()
以及equals()
有效和正确的内容,但是我无法弄清为什么我们需要覆盖这两种方法。
我该如何决定有效地实施这些方法?
最近,我通读了这份 Developer Works文档。
该文档主要是关于定义hashCode()
以及equals()
有效和正确的内容,但是我无法弄清为什么我们需要覆盖这两种方法。
我该如何决定有效地实施这些方法?
Answers:
约书亚·布洛赫(Joshua Bloch)谈有效Java
您必须在每个覆盖equals()的类中覆盖hashCode()。否则将导致违反Object.hashCode()的常规协定,这将阻止您的类与所有基于哈希的集合(包括HashMap,HashSet和Hashtable)一起正常运行。
让我们尝试通过一个示例来理解它,如果我们在equals()
不进行覆盖的情况下进行覆盖hashCode()
而尝试使用Map
。
假设我们有一个类像这样那样的两个对象MyClass
是相等的,如果他们importantField
等于(与hashCode()
和equals()
Eclipse生成)
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;
}
public String getEqualField() {
return importantField;
}
public String getAnotherField() {
return 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;
}
}
仅覆盖 equals
如果仅equals
重写,则在您myMap.put(first,someValue)
第一次调用时将哈希到某个存储桶,而在调用myMap.put(second,someOtherValue)
时将哈希到其他存储桶(因为它们具有不同的hashCode
)。因此,尽管它们相等,但由于它们没有散列到同一个存储桶中,因此地图无法实现,因此它们都留在了地图中。
尽管不必重写equals()
就可以覆盖hashCode()
,但是让我们看看在这种特殊情况下会发生什么,在这种情况下,我们知道的两个对象MyClass
相等就可以了importantField
,但是我们不覆盖equals()
。
仅覆盖 hashCode
想象你有这个
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
如果仅覆盖,hashCode
则在调用myMap.put(first,someValue)
它时会先进行计算,hashCode
然后将其计算并存储在给定存储桶中。然后,当您调用myMap.put(second,someOtherValue)
它时,应按照地图文档将其替换为第二个因为它们相等(根据业务要求)。
但问题是,等于没有重新定义,所以当图哈希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
是错的。hashCode
如果您的类重写equals
但reverse不正确,则需要重写。
equals
才能违反javadoc中阐明的约定Object
:“如果根据equals(Object)
方法两个对象相等,则hashCode
在两个对象中的每个对象上调用方法必须产生相同的整数结果。” 当然,并非所有合同的所有部分都在所有代码中得到行使,但从形式上来讲,这仍然是违规行为,我认为这是一个等待发生的错误。
集合(例如HashMap
和HashSet
使用对象的哈希码值)确定应如何将其存储在集合中,以及哈希码再次使用以便在对象的集合中定位该对象。
散列检索分为两个步骤:
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
现在取消注释hashcode()
方法,执行相同的操作,输出将是:
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
现在可以看到为什么如果两个对象被认为相等,那么它们的哈希码也必须相等?否则,您将永远无法找到对象,因为Object类中的默认
哈希码方法实际上总是为每个对象提供一个唯一的编号,即使该equals()
方法被覆盖为将两个或更多对象视为相等也是如此。如果对象的哈希码不反映对象的相等程度,则无所谓。所以再来一次:如果两个对象相等,则它们的
哈希码也必须相等。
您必须在每个覆盖equals()的类中覆盖hashCode()。否则将导致违反Object.hashCode()的常规协定,这将阻止您的类与所有基于哈希的集合(包括HashMap,HashSet和Hashtable)一起正常运行。
Joshua Bloch撰写的 来自Effective Java的文章
通过定义equals()
并hashCode()
保持一致,可以提高类作为基于哈希的集合中的键的可用性。正如hashCode的API文档所解释的那样:“支持此方法是为了使哈希表(例如由提供的哈希表)受益java.util.Hashtable
。”
关于如何有效地实现这些方法的问题的最佳答案是建议您阅读《有效Java》的第3章。
hashCode()
。
简而言之,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");
没有压倒一切就等于:
正确?好吧,如果这就是您想要的。但是,假设我们希望具有相同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的辅助方法
身份不是平等。
==
测试身份。equals(Object obj)
方法比较相等性测试(即,我们需要通过覆盖方法来告诉相等性)为什么需要重写Java中的equals和hashCode方法?
首先,我们必须了解equals方法的使用。
为了标识两个对象之间的差异,我们需要重写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之类的数据结构中。
假设我们有Customer
如上所述的equals方法,
customer1.equals(customer2); // returns true by our own logic
在将数据存储在存储桶中时使用数据结构(存储桶是文件夹的漂亮名称)。如果我们使用内置的哈希技术,则对于上述两个客户,它将生成两个不同的哈希码。因此,我们将相同的对象存储在两个不同的位置。为避免此类问题,我们还应根据以下原则重写hashCode方法。
好的,让我用非常简单的词来解释这个概念。
首先,从更广泛的角度来看,我们有集合,而哈希图是集合中的数据结构之一。
为了理解为什么我们必须同时覆盖equals和hashcode方法,如果需要首先了解什么是hashmap和做什么。
哈希图是一种数据结构,它以数组形式存储数据的键值对。假设a [],其中“ a”中的每个元素都是一个键值对。
同样,上述数组中的每个索引都可以链接列表,从而在一个索引处具有多个值。
现在为什么要使用哈希图?如果我们必须在一个大型数组中搜索,然后搜索每个数组是否效率不高,那么什么哈希技术告诉我们,可以使用某种逻辑对数组进行预处理,并根据该逻辑对元素进行分组,即哈希
例如:我们有数组1,2,3,4,5,6,7,8,9,10,11,并且我们应用了哈希函数mod 10,因此1,11将被分组在一起。因此,如果必须在先前的数组中搜索11,则必须迭代整个数组,但是当我们对它进行分组时,我们限制了迭代范围,从而提高了速度。为了简单起见,可以将用于存储所有上述信息的数据结构视为2d数组
现在,除了上面的哈希图,还告诉它不会在其中添加任何重复项。这就是为什么我们必须重写equals和hashcode的主要原因
因此,当其说明哈希表的内部工作时,我们需要查找哈希表有哪些方法,以及哈希表如何遵循上述我所解释的规则
因此,哈希图具有称为put(K,V)的方法,根据哈希图,哈希图应遵循上述有效分配数组且不添加任何重复项的规则
因此,put的工作是首先为给定的键生成哈希码,以决定该值应该进入哪个索引。如果该索引处不存在任何索引,则将在该索引处添加新值(如果该索引已经存在)然后应在链接列表的末尾在该索引处添加新值。但请记住,根据哈希图的预期行为,不应添加任何重复项。假设您有两个Integer对象aa = 11,bb = 11。与从对象类派生的每个对象一样,比较两个对象的默认实现是比较引用而不是对象内部的值。因此,在上述情况下,尽管在语义上相等,但都将通过相等性测试,并且可能存在具有相同哈希码和相同值的两个对象,从而产生重复项。如果覆盖,则可以避免添加重复项。您也可以参考详细工作
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;
Integer 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());
}
}
hashCode()
:
如果仅覆盖哈希码方法,将不会发生任何事情。因为它总是将hashCode
每个对象作为Object类返回new 。
equals()
:
如果仅覆盖equal方法,a.equals(b)
则为true表示hashCode
a和b的a必须相同但不会发生。因为您没有覆盖hashCode
方法。
注意: hashCode()
Object类的方法总是返回newhashCode
为每个对象。
因此,当您需要在基于哈希的集合中使用对象时,必须同时覆盖equals()
和hashCode()
。
Java制定了一条规则
“如果使用Object类equals方法使两个对象相等,则hashcode方法应为这两个对象提供相同的值。”
因此,如果在我们的类中重写,则我们equals()
也应该重写hashcode()
方法以遵循此规则。例如,equals()
和中的两种方法hashcode()
都用于Hashtable
将值存储为键值对。如果我们覆盖一个而不是另一个,则Hashtable
如果我们使用这样的对象作为键,则可能无法按我们期望的那样工作。
添加到@Lombo的答案
您何时需要覆盖equals()?
Object的equals()的默认实现是
public boolean equals(Object obj) {
return (this == obj);
}
这意味着两个对象只有在它们具有相同的内存地址时才被认为是相等的;只有当您将一个对象与其自身进行比较时,这才是正确的。
但是,如果两个对象的一个或多个属性具有相同的值,则可能要考虑两个对象相同(请参阅@Lombo答案中给出的示例)。
因此,equals()
在这些情况下您将优先考虑,并给出自己的平等条件。
我已经成功实现了equals(),并且运行良好,为什么他们还要覆盖hashCode()?
好。只要您在用户定义的类上不使用基于“哈希”的Collections,就可以了。但是在将来的某个时候,您可能想使用,HashMap
或者HashSet
如果您不这样做override
并“正确实现” hashCode(),那么这些基于哈希的集合将无法按预期工作。
覆盖仅等于(@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
。仅当值相同时,才会继续检查同一存储桶中的相等性。
但是这里的hashCode对于这两个对象是不同的(因为它们具有与默认实现不同的内存地址)。因此,它甚至都不关心检查是否相等。
如果在覆盖的equals()方法中有一个断点,则如果它们具有不同的hashCodes,则不会介入。
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,则必须在存储桶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
}
hashCode()
确定桶的equals()
方式计算哈希值,并使用方法来查找桶中是否已存在该值。如果没有,它将被添加,否则它将被当前值替换hashCode()
查找条目(存储桶)并
equals()
在Entry中查找值如果两者都被覆盖,
地图< 甲 >
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
如果不等于
地图< 甲 >
Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..
如果hashCode没有被覆盖
地图< 甲 >
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平等合同
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()引起的问题
该问题是由不可覆盖的方法引起的hashCode()
。equals()
和之间的合同hashCode()
是:
如果两个对象具有相同的哈希码,则它们可以相等或可以不相等。
public int hashCode(){
return this.color.hashCode();
}
在使用Value Objects时很有用。以下是波特兰模式存储库的摘录:
价值对象的示例包括数字,日期,金额和字符串之类的东西。通常,它们是被广泛使用的小物体。他们的身份是基于他们的状态,而不是他们的对象身份。这样,您可以具有相同概念值对象的多个副本。
因此,我可以有一个代表1998年1月16日这个日期的对象的多个副本。这些副本中的任何一个都是相等的。对于这样的小对象,创建新对象并四处移动通常要容易得多,而不是依靠单个对象来表示日期。
在Java中,值对象应始终覆盖.equals()(在Smalltalk中为=)。(请记住也要覆盖.hashCode()。)
equals和hashcode方法在对象类中定义。默认情况下,如果equals方法返回true,则系统将继续检查哈希码的值。如果两个对象的哈希码也相同,则这些对象将被视为相同。因此,如果覆盖仅equals方法,则即使覆盖的equals方法指示2个对象相等,系统定义的哈希码也可能不会指示2个对象相等。因此,我们还需要覆盖哈希码。
true
的东西equals
将不被视为匹配。另一方面,如果发生收集,请注意事物不能具有相同的哈希码,它们很可能不会注意到它们是相等的。
Java中的Equals和Hashcode方法
它们是java.lang.Object类的方法,该类是所有类(以及自定义类以及Java API中定义的其他类)的超类。
实现方式:
公共布尔等于(Object obj)
public int hashCode()
公共布尔等于(Object 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,只要未修改对象的equals比较中使用的任何信息,对x.equals(y)的多次调用将始终返回true或始终返回false。
对于任何非null参考值x,x.equals(null)应该返回false。
public int hashCode()
此方法返回在其上调用此方法的对象的哈希码值。此方法以整数形式返回哈希码值,并且支持基于哈希的收集类(例如Hashtable,HashMap,HashSet等)。此方法必须在覆盖equals方法的每个类中被覆盖。
hashCode的一般约定为:
在Java应用程序的执行过程中,只要在同一对象上多次调用它,则hashCode方法必须一致地返回相同的整数,前提是未修改该对象的equals比较中使用的信息。
从一个应用程序的执行到同一应用程序的另一执行,此整数不必保持一致。
如果根据equals(Object)方法,两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
根据equals(java.lang.Object)方法,如果两个对象不相等,则不需要在两个对象中的每个对象上调用hashCode方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
只要相等,相等的对象就必须产生相同的哈希码,但是不相等的对象不必产生不同的哈希码。
资源:
在下面的示例中,如果您注释掉Person类中的equals或hashcode覆盖,则此代码将无法查找Tom的顺序。使用哈希码的默认实现可能会导致哈希表查找失败。
我下面的内容是一个简化的代码,可以按人员列出人们的命令。人员被用作哈希表中的键。
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);
}
}
}
字符串类和包装器类对equals()
和的实现不同hashCode()
方法与对象类不同。Object类的equals()方法比较对象的引用,而不是内容。无论内容是否相同,Object类的hashCode()方法都会为每个单个对象返回不同的哈希码。
当您使用Map集合并且键为Persistent类型,StringBuffer / builder类型时,这会导致问题。由于它们不像String类那样不重写equals()和hashCode(),因此,当您比较两个不同的对象时,即使它们具有相同的内容,equals()也会返回false。它将使hashMap存储相同的内容密钥。存储相同的内容密钥意味着违反Map的规则,因为Map根本不允许重复的密钥。因此,您可以在类中重写equals()和hashCode()方法,并提供实现(IDE可以生成这些方法),以便它们与String的equals()和hashCode()相同,并防止使用相同的内容键。
您必须将hashCode()方法与equals()一起重写,因为equals()根据哈希码工作。
此外,将hashCode()方法与equals()一起重写有助于完整的equals()-hashCode()契约:“如果两个对象相等,则它们必须具有相同的哈希码。”
什么时候需要为hashCode()编写自定义实现?
众所周知,HashMap的内部工作是基于散列原理的。在某些存储桶中存储条目集。您可以根据需要自定义hashCode()实现,以便可以将相同的类别对象存储到相同的索引中。当您使用put(k,v)
方法将值存储到Map集合中时,put()的内部实现是:
put(k, v){
hash(k);
index=hash & (n-1);
}
就是说,它生成索引,并且索引是基于特定键对象的哈希码生成的。因此,使此方法根据您的要求生成哈希码,因为相同的哈希码条目集将存储在相同的存储桶或索引中。
而已!
hashCode()
方法用于获取给定对象的唯一整数。当此对象需要存储在某些对象HashTable
(HashMap
如数据结构)中时,该整数用于确定存储桶的位置。默认情况下,对象的hashCode()
方法返回存储对象的内存地址的整数表示形式。
在hashCode()
当我们将它们插入到一个对象的方法使用HashTable
,HashMap
或HashSet
。有关HashTables
Wikipedia.org的更多信息,以供参考。
要在地图数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义的数据类型,hashCode()
则键的确定将确定内部存储对象的位置。当还需要从地图查找对象时,键的哈希码将确定在哪里搜索对象。
哈希码仅在内部指向某个“区域”(或列表,存储桶等)。由于不同的密钥对象可能具有相同的哈希码,因此哈希码本身不能保证找到正确的密钥。在HashTable
然后遍历这个区域(具有相同的散列码的所有键),并使用密钥的equals()
方法,找到正确的键。找到右键后,将返回为该键存储的对象。
因此,正如我们所看到的,当在中存储和查找对象时,使用hashCode()
和equals()
方法的组合HashTable
。
笔记:
始终使用对象的相同属性来生成hashCode()
和equals()
两者都使用。在本例中,我们使用了员工ID。
equals()
必须保持一致(如果未修改对象,则必须保持返回相同的值)。
无论何时a.equals(b)
,则a.hashCode()
必须与b.hashCode()
。
如果覆盖一个,则应覆盖另一个。
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
hashCode()
不用于为每个对象返回唯一的整数。那是不可能的。您自己在第四段的第二句中与此矛盾。
恕我直言,这是按规则说的-如果两个对象相等,则它们应具有相同的哈希值,即相等的对象应产生相等的哈希值。
上面给出,对象中的默认equals()==,它对地址进行比较,hashCode()返回整数形式的地址(实际地址上的哈希值),这对于不同的Object也是不同的。
如果需要在基于Hash的集合中使用自定义对象,则需要覆盖equals()和hashCode(),例如如果我想维护Employee Objects的HashSet,如果我不使用更强大的hashCode和equals我可能最终会覆盖两个不同的Employee对象,这在我将年龄用作hashCode()时发生,但是我应该使用唯一的值,它可以是Employee ID。
当您要在Map中存储和检索自定义对象作为键时,则应始终在自定义Object中覆盖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将被视为仅一个对象,并且map
大小将仅为1,因为它们相等。
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)用于比较地址比较,这就是为什么在测试类中,如果您比较两个对象,然后equals方法给出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
Bah-“您必须在每个覆盖equals()的类中覆盖hashCode()。”
[摘自Joshua Bloch的《有效Java》?
这不是错误的方法吗?覆盖hashCode可能意味着您正在编写哈希键类,但是覆盖等号肯定不是。有许多类没有用作哈希键,但出于某些其他原因,确实希望使用逻辑相等性测试方法。如果为它选择“等于”,则可能由于过分热衷于此规则而被授权编写hashCode实现。所要做的就是在代码库中添加未经测试的代码,这是邪恶的等待,将来使某人绊倒。同样,编写不需要的代码也是抗敏捷的。这是错误的(想法产生的想法可能与您手工制作的等式不兼容)。
当然,他们应该已经在编写用作键的对象上强制使用了接口吗?无论如何,Object绝对不应该提供默认的hashCode()和equals()imho。可能鼓励了许多破碎的哈希集合。
但是无论如何,我认为“规则”是写在前面的。同时,我将继续避免在相等性测试方法中使用“等于” :-(