Sha*_*shi 355 java equals hashcode
最近我读了这个 Developer Works文档.
该文档是关于定义hashCode()和equals()有效和正确的,但我无法弄清楚为什么我们需要覆盖这两种方法.
如何有效地实施这些方法?
Lom*_*mbo 497
Joshua Bloch谈有效Java
您必须覆盖覆盖equals()的每个类中的hashCode().如果不这样做,将导致违反Object.hashCode()的常规合同,这将阻止您的类与所有基于散列的集合(包括HashMap,HashSet和Hashtable)一起正常运行.
让我们试着用一个例子来理解它,如果我们覆盖equals()而不覆盖hashCode()并尝试使用a 会发生什么Map.
假设我们有一个这样的类,MyClass如果它们importantField相等(由eclipse生成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;
}
}
Run Code Online (Sandbox Code Playgroud)
仅覆盖 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");
Run Code Online (Sandbox Code Playgroud)
如果您只覆盖,hashCode那么当您myMap.put(first,someValue)首先调用它时,计算它hashCode并将其存储在给定的存储桶中.然后,当您调用myMap.put(second,someOtherValue)它时,应根据Map文档首先替换为second,因为它们是相等的(根据业务要求).
但问题是,等于没有重新定义,所以当图哈希second通过桶和迭代,查找是否有一个对象k,从而second.equals(k)是事实,就不会找到任何的second.equals(first)会false.
希望很清楚
raj*_*i.. 225
集合,例如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;
}
*/
}
Run Code Online (Sandbox Code Playgroud)
现在创建一个类,将Employee对象插入到a中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());
}
}
Run Code Online (Sandbox Code Playgroud)
它将打印以下内容:
false
employee.hashCode(): 321755204 employee2.hashCode():375890482
Run Code Online (Sandbox Code Playgroud)
现在取消注释hashcode()方法,执行相同的操作,输出将是:
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
Run Code Online (Sandbox Code Playgroud)
现在你能看出为什么如果两个对象被认为是相等的,它们的哈希码 s也必须相等?否则,您永远无法找到该对象,因为类Object中的默认
哈希码方法实际上总是为每个对象提供唯一的编号,即使该equals()方法被覆盖的方式使得两个或多个对象被认为是相等的.如果对象的哈希码不能反映出来,那么它们的平等程度并不重要.再一次:如果两个对象相等,那么它们的
哈希码也必须相等.
Jua*_*nZe 48
您必须覆盖覆盖equals()的每个类中的hashCode().如果不这样做,将导致违反Object.hashCode()的常规合同,这将阻止您的类与所有基于散列的集合(包括HashMap,HashSet和Hashtable)一起正常运行.
来自Effective Java,来自Joshua Bloch
通过定义equals()和hashCode()一致,您可以提高类的可用性作为基于散列的集合中的键.正如hashCode的API文档所解释的那样:"支持这种方法是为了哈希表的好处,例如那些提供的哈希表java.util.Hashtable."
关于如何有效实现这些方法的问题的最佳答案是建议您阅读Effective Java的第3章.
cru*_*dog 21
简单地说,Object中的equals-method检查引用相等性,其中当属性相等时,类的两个实例在语义上仍然相等.例如,将对象放入使用equals和hashcode的容器(例如HashMap和Set)时,这很重要.假设我们有一个类:
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
}
Run Code Online (Sandbox Code Playgroud)
我们使用相同的id创建两个实例:
Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");
Run Code Online (Sandbox Code Playgroud)
没有压倒平等,我们得到:
正确?好吧,也许,如果这是你想要的.但是,假设我们希望具有相同id的对象成为同一个对象,无论它是两个不同的实例.我们覆盖equals(和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();
}
}
Run Code Online (Sandbox Code Playgroud)
至于实现equals和hashcode,我可以推荐使用Guava的帮助方法
Pre*_*raj 17
身份不是平等.
==测试身份.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
Run Code Online (Sandbox Code Playgroud)
现在hashCode方法可以轻松理解.
hashCode生成整数,以便将对象存储在HashMap,HashSet等数据结构中.
假设我们有Customer如上所述的覆盖等于方法,
customer1.equals(customer2); // returns true by our own logic
Run Code Online (Sandbox Code Playgroud)
在我们将对象存储在存储桶中时使用数据结构(存储桶是文件夹的奇特名称).如果我们使用内置哈希技术,对于上面两个客户,它会生成两个不同的哈希码.所以我们在两个不同的地方存储相同的相同对象.为了避免这种问题,我们应该基于以下原则覆盖hashCode方法.
Che*_*tan 12
好的,让我用非常简单的话来解释这个概念.
首先,从更广泛的角度来看,我们有集合,而hashmap是集合中的数据结构之一.
要理解为什么我们必须重写equals和hashcode方法,如果需要首先了解什么是hashmap以及做什么.
散列映射是以数组方式存储数据的键值对的数据结构.让我们说一个[],其中'a'中的每个元素都是一个键值对.
此外,上述数组中的每个索引都可以是链表,从而在一个索引处具有多个值.
现在为什么要使用hashmap?如果我们必须在一个大型数组中搜索,那么搜索每个它们将不会有效,那么什么哈希技术告诉我们让我们用一些逻辑预处理数组并根据该逻辑对元素进行分组即哈希
例如:我们有数组1,2,3,4,5,6,7,8,9,10,11,我们应用一个散列函数mod 10,因此1,11将被分组在一起.因此,如果我们必须在先前的数组中搜索11,那么我们将不得不迭代整个数组,但是当我们对它进行分组时,我们限制了迭代的范围,从而提高了速度.为简单起见,用于存储所有上述信息的数据结构可以被认为是2d数组
现在除了上面的hashmap之外还告诉它不会在其中添加任何Duplicates.这是我们必须覆盖equals和hashcode的主要原因
所以当它说解释hashmap的内部工作时,我们需要找到hashmap有哪些方法,以及它如何遵循我上面解释的上述规则
所以hashmap有一个叫做put(K,V)的方法,根据hashmap它应该遵循上面的规则来有效地分配数组而不添加任何重复
所以做的是它首先会为给定的密钥生成哈希码,以决定该值应该进入哪个索引.如果该索引中没有任何内容,那么新值将被添加到那里,如果那里已存在某些东西然后应在该索引的链表结束后添加新值.但请记住,不应根据hashmap的所需行为添加重复项.所以假设你有两个整数对象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());
}
}
Run Code Online (Sandbox Code Playgroud)
小智 11
hashCode() :
如果您只覆盖哈希码方法,则不会发生任何事情.因为它总是hashCode为每个对象返回新的Object类.
equals() :
如果你只覆盖等于方法,a.equals(b)则为true表示hashCodea和b必须相同但不会发生.因为你没有覆盖hashCode方法.
注意: hashCode()Object类的方法总是hashCode为每个对象返回new .
因此,当您需要在基于散列的集合中使用对象时,必须覆盖两者equals()和hashCode().
为什么我们重写equals()方法
在 Java 中,我们不能重载 ==、+=、-+ 等运算符的行为方式。他们以某种方式行事。因此,让我们在这里关注我们的案例中的运算符 ==。
运算符 == 的工作原理。
它检查我们比较的 2 个引用是否指向内存中的同一个实例。==仅当这 2 个引用代表内存中的同一实例时,运算符才会解析为 true。
所以现在让我们考虑下面的例子
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
Run Code Online (Sandbox Code Playgroud)
因此,假设在您的程序中,您在不同的地方构建了 2 个 Person 对象,并且您希望对它们进行比较。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
Run Code Online (Sandbox Code Playgroud)
从业务角度来看,这两个对象看起来一样吗?对于 JVM,它们不一样。由于它们都是使用new关键字创建的,因此这些实例位于内存中的不同段中。因此运算符 == 将返回false
但是如果我们不能覆盖 == 操作符,我们怎么能告诉 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);
}
Run Code Online (Sandbox Code Playgroud)
让我们回到之前的例子
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!
Run Code Online (Sandbox Code Playgroud)
所以我们不能重载 == 运算符以我们想要的方式比较对象,但 Java 给了我们另一种方式,equals()我们可以根据需要覆盖的方法。
但是请记住,如果我们不在.equals()我们的类中提供我们的自定义版本(又名覆盖),那么.equals()来自 Object 类和==运算符的预定义的行为将完全相同。
equals()从 Object 继承的默认方法将检查两个比较的实例在内存中是否相同!
为什么我们重写hashCode()方法
Java 中的一些数据结构(如 HashSet、HashMap)根据应用于这些元素的散列函数来存储它们的元素。哈希函数是hashCode()
如果我们可以选择覆盖.equals()方法,那么我们也必须选择覆盖hashCode()方法。这是有原因的。
hashCode()继承自 Object 的默认实现认为内存中的所有对象都是唯一的!
让我们回到那些散列数据结构。这些数据结构有一个规则。
HashSet 不能包含重复值,HashMap 不能包含重复键
HashSet 在幕后使用 HashMap 实现,其中 HashSet 的每个值都存储为 HashMap 中的键。
所以我们必须了解 HashMap 是如何工作的。
简单来说,HashMap 是一个具有一些桶的本机数组。每个桶都有一个链表。在那个链表中,我们的密钥被存储。HashMap 通过应用hashCode()方法为每个键定位正确的链表,然后它遍历该链表的所有元素,equals()并对这些元素中的每一个应用方法来检查该元素是否已经包含在那里。不允许重复键。
当我们在 HashMap 中放入一些东西时,键存储在这些链表之一中。该键将存储在哪个链表中,由该hashCode()键上的方法的结果显示。因此,如果key1.hashCode()结果为 4,则该 key1 将存储在数组的第 4 个存储桶中,即存在于那里的链表中。
默认情况下,hashCode()方法为每个不同的实例返回不同的结果。如果我们的默认equals()行为类似于 == ,它将内存中的所有实例视为不同的对象,我们就没有任何问题。
但是在我们之前的示例中,我们说过如果年龄和姓名匹配,我们希望 Person 实例被视为相等。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
Run Code Online (Sandbox Code Playgroud)
现在让我们创建一个映射来存储这些实例作为键,一些字符串作为对值
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
Run Code Online (Sandbox Code Playgroud)
在 Person 类中,我们没有覆盖hashCode方法,但我们覆盖了equals方法。由于默认hashCode为不同的 java 实例提供不同的结果,person1.hashCode()并且person2.hashCode()有很大的机会得到不同的结果。
我们的地图可能以不同链表中的那些人结束。
这违反了 HashMap 的逻辑
一个 HashMap 不允许有多个相等的键!
但是我们现在有了,原因是hashCode()从对象类继承的默认值还不够。不是在我们覆盖equals()Person 类上的方法之后。
这就是为什么我们必须在重写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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在让我们再次尝试将这些键保存在我们的 HashMap 中
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
Run Code Online (Sandbox Code Playgroud)
person1.hashCode()并且person2.hashCode()肯定会一样。假设它是0。
HashMap 将转到存储桶 0,在该 LinkedList 中,将 person1 保存为值为“1”的键。对于第二个 put HashMap 足够智能,当它再次进入存储桶 0 以保存值为“2”的 person2 键时,它将看到另一个相等的键已经存在于那里。所以它会覆盖以前的密钥。所以最终只有 person2 键存在于我们的 HashMap 中。
现在我们符合 Hash Map 的规则,即不允许多个相等的键!
Java提出了一个规则
"如果两个对象使用Object类equals方法相等,那么hashcode方法应该为这两个对象赋予相同的值."
所以,如果在我们的类中我们覆盖,equals()我们应该覆盖hashcode()方法也遵循这个规则.这两种方法,equals()以及hashcode(),使用在Hashtable,例如,存储值作为键-值对.如果我们覆盖一个而不是另一个,那么Hashtable如果我们使用这样的对象作为键,则可能无法按我们的意愿工作.
为了使用我们自己的类对象作为HashMap,Hashtable等集合中的键,我们应该通过了解集合的内部工作来覆盖这两个方法(hashCode()和equals()).否则,它会导致我们不期望的错误结果.
添加到@Lombo的答案
你什么时候需要覆盖equals()?
Object的equals()的默认实现是
public boolean equals(Object obj) {
return (this == obj);
}
Run Code Online (Sandbox Code Playgroud)
这意味着只有当两个对象具有相同的内存地址时才会被认为是相同的,只有在您将对象与自身进行比较时才会成立.
但是如果两个对象的一个或多个属性具有相同的值,则可能需要考虑两个对象(请参阅@Lombo答案中给出的示例).
所以你会equals()在这些情况下重写,你会给自己平等的条件.
我已经成功实现了equals()并且它工作得很好.所以他们为什么要求覆盖hashCode()呢?
好吧.只要你不在用户定义的类上使用基于"Hash"的集合,就可以了.但是将来某些时候你可能想要使用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?
Run Code Online (Sandbox Code Playgroud)
首先,HashMap检查hashCode是否与之second相同first.仅当值相同时,才会继续检查同一存储桶中的相等性.
但是这里的hashCode对于这两个对象是不同的(因为它们具有不同的内存地址 - 来自默认实现).因此,它甚至不会检查是否平等.
如果在重写的equals()方法中有一个断点,如果它们有不同的hashCodes,它就不会介入.
contains()检查hashCode(),只有它们是相同的,它会调用你的equals()方法.
为什么我们不能让HashMap检查所有桶中的相等性?所以我没有必要覆盖hashCode()!!
那么你就错过了基于Hash的集合.考虑以下 :
Your hashCode() implementation : intObject%9.
Run Code Online (Sandbox Code Playgroud)
以下是以桶形式存储的密钥.
Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...
Run Code Online (Sandbox Code Playgroud)
说,你想知道地图是否包含密钥10.你想搜索所有的桶吗?或者你想只搜索一个桶吗?
基于hashCode,您将确定如果存在10,则它必须存在于Bucket 1中.因此只会搜索Bucket 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
}
Run Code Online (Sandbox Code Playgroud)
hashCode()确定存储桶来计算哈希值,并使用equals()方法查找该值是否已存在于存储桶中.如果没有,它将被添加,否则它将被替换为当前值hashCode()查找Entry(存储桶)并
equals()在Entry中查找值如果两者都被覆盖,
地图< 甲 >
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
Run Code Online (Sandbox Code Playgroud)
如果不等于等于
地图< 甲 >
Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..
Run Code Online (Sandbox Code Playgroud)
如果没有覆盖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...
Run Code Online (Sandbox Code Playgroud)
HashCode平等合同
小智 5
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")));
}
}
Run Code Online (Sandbox Code Playgroud)
未找到绿色汽车
2. hashCode()引起的问题
该问题是由未重写的方法引起的hashCode()。equals()和之间的合同hashCode()是:
如果两个对象具有相同的哈希码,则它们可能相等也可能不相等。
public int hashCode(){
return this.color.hashCode();
}
Run Code Online (Sandbox Code Playgroud)| 归档时间: |
|
| 查看次数: |
394814 次 |
| 最近记录: |