为什么我需要覆盖Java中的equals和hashCode方法?

时间:2010-02-15 11:18:00

标签: java equals hashcode

最近我读了这篇文章 Developer Works Document

该文档完全是关于有效和正确地定义hashCode()equals(),但是我无法弄清楚为什么我们需要覆盖这两种方法。

如何有效地实施这些方法?

30 个答案:

答案 0 :(得分:480)

Joshua Bloch谈到Effective Java

  

您必须覆盖覆盖equals()的每个类中的hashCode()。如果不这样做,将导致违反Object.hashCode()的常规合同,这将阻止您的类与所有基于散列的集合(包括HashMap,HashSet和Hashtable)一起正常运行。

让我们尝试理解一下,如果我们覆盖equals()而不覆盖hashCode()并尝试使用Map会发生什么。

假设我们有一个这样的类,如果MyClass相等(importantFieldhashCode()生成的equals()),则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() { {1}}相同,但我们不会覆盖hashCode

仅覆盖MyClass first = new MyClass("a","first"); MyClass second = new MyClass("a","second");

想象一下,你有这个

hashCode

如果您只覆盖myMap.put(first,someValue),那么当您首先调用hashCode时,会计算其myMap.put(second,someOtherValue)并将其存储在给定的存储分区中。然后,当您致电second时,它应该根据Map Documentation首先替换为秒,因为它们是相等的(根据业务要求)。

但问题是equals没有重新定义,所以当地图哈希k并在桶中迭代时,查看是否有second.equals(k)对象second.equals(first)为真,它赢了找不到false {{1}}。

希望很清楚

答案 1 :(得分:181)

HashMapHashSet等集合使用对象的哈希码值来确定它应该如何存储在集合中,以及哈希码来定位对象 在其收藏中。

散列检索分为两步:

  1. 找到合适的存储桶(使用hashCode()
  2. 在存储桶中搜索正确的元素(使用equals()
  3. 以下是我们为什么要覆盖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中的 hashcode 方法几乎总是会出现一个唯一的数字 对于每个对象,即使以{2}方式覆盖equals()方法也是如此 或更多的对象被认为是平等的。如果对象的平等程度并不重要 他们的哈希码并不反映这一点。再来一次:如果两个对象相等,那就是他们的 哈希码也必须相同。

答案 2 :(得分:48)

  

您必须在每个中覆盖hashCode()   覆盖equals()的类。失败   这样做会导致违反   一般合同   Object.hashCode(),它会阻止   你的班级正常运作   结合所有基于哈希的   集合,包括HashMap,   HashSet和Hashtable。

     

来自 Effective Java ,来自Joshua Bloch

通过一致地定义equals()hashCode(),您可以提高类的可用性,使其成为基于散列的集合中的键。正如hashCode的API文档所解释的那样:“支持这种方法的好处是哈希表,例如java.util.Hashtable提供的哈希表。”

关于如何有效实施这些方法的问题的最佳答案是建议您阅读Effective Java的第3章。

答案 3 :(得分:20)

简单地说,Object中的equals-method检查引用相等性,其中当属性相等时,类的两个实例在语义上仍然相等。将对象放入使用equals和hashcode的容器(例如HashMapSet)时,这很重要。假设我们有一个类:

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的对象成为同一个对象,无论它是两个不同的实例。我们覆盖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();
    }
}

至于实现equals和hashcode,我建议使用Guava's helper methods

答案 4 :(得分:15)

身份不平等。

  • 等于运算符==测试标识。
  • 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的方法,

customer1.equals(customer2);  // returns true by our own logic

在我们将对象存储在存储桶中时使用数据结构(存储桶是文件夹的奇特名称)。如果我们使用内置哈希技术,对于上面两个客户,它会生成两个不同的哈希码。所以我们在两个不同的地方存储相同的相同对象。为了避免这种问题,我们应该基于以下原则覆盖hashCode方法。

  • 不相等的实例可能具有相同的哈希码。
  • 相等的实例应返回相同的哈希码。

答案 5 :(得分:12)

好的,让我用非常简单的话来解释这个概念。

首先,从更广泛的角度来看,我们有集合,而hashmap是集合中的数据结构之一。

要理解为什么我们必须覆盖equals和hashcode方法,如果需要先了解什么是hashmap以及做什么。

散列映射是一种数据结构,它以数组方式存储数据的键值对。让我们说a [],其中'a'中的每个元素都是一个键值对。

上述数组中的每个索引都可以是链表,从而在一个索引处具有多个值。

现在为什么要使用hashmap? 如果我们必须在一个大型数组中进行搜索,那么搜索每个它们将不会有效,那么什么哈希技术告诉我们让我们用一些逻辑预处理数组并根据该逻辑对元素进行分组,即Hashing

例如:我们有数组1,2,3,4,5,6,7,8,9,10,11,我们应用了一个散列函数mod 10,因此1,11将被分组在一起。因此,如果我们必须在先前的数组中搜索11,那么我们将不得不迭代整个数组,但是当我们对它进行分组时,我们限制了迭代的范围,从而提高了速度。用于存储所有上述信息的数据结构可以被认为是简单的2d数组

现在除了上面的hashmap之外还告诉它不会在其中添加任何重复项。这是我们必须覆盖equals和hashcode

的主要原因

所以当它说解释hashmap的内部工作时,我们需要找到hashmap有哪些方法,以及它如何遵循我上面解释的上述规则

所以hashmap有一个名为put(K,V)的方法,并且根据hashmap它应该遵循上面的规则来有效地分配数组而不添加任何重复

所以put会做的是它首先会为给定的密钥生成哈希码,以决定该值应该进入哪个索引。如果该索引中没有任何内容,那么新值将被添加到那里,如果某些东西已经存在然后在那里出现新值应该在该索引的链表结束之后添加。但请记住,不应根据hashmap的所需行为添加重复项。所以假设你有两个整数对象aa = 11,bb = 11。 作为从对象类派生的每个对象,比较两个对象的默认实现是它比较对象内部的引用而不是值。因此,在上述情况下,虽然在语义上相等但都会使相等性测试失败,并且可能存在两个相同哈希码和相同值的对象,从而产生重复。如果我们覆盖,那么我们可以避免添加重复。 您也可以参考Detail working

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());


}

}

答案 6 :(得分:11)

hashCode()

如果您只覆盖哈希码方法,则不会发生任何事情。因为它总是为每个对象返回新的hashCode作为Object类。

equals()

如果你只覆盖相等的方法,a.equals(b)为真,则意味着a和b的hashCode必须相同但不会发生。因为您没有覆盖hashCode方法。

注意:Object类的hashCode()方法总是为每个对象返回新的hashCode

因此,当您需要在基于散列的集合中使用对象时,必须覆盖equals()hashCode()

答案 7 :(得分:6)

因为如果你不覆盖它们,你将使用Object中的默认实现。

鉴于实例相等和hascode值通常需要知道构成对象的内容,因此通常需要在类中重新定义它们以具有任何实际意义。

答案 8 :(得分:6)

添加@Lombo的回答

您何时需要覆盖equals()?

Object的equals()的默认实现是

public boolean equals(Object obj) {
        return (this == obj);
}

这意味着只有两个对象具有相同的内存地址才会被认为是相同的,只有当你这样做时才会成立 将对象与自身进行比较。

但是如果两个对象具有相同的值,则可能需要考虑两个对象 或更多他们的属性(参考@Lombo答案中给出的例子)。

所以你会在这些情况下覆盖equals(),你会给自己平等的条件。

我已经成功实现了equals()并且它工作得很好。所以他们为什么要求覆盖hashCode()?

嗯。只要您不在用户定义的类上使用基于“Hash”的集合,就可以了。 但是将来某个时候你可能想要使用HashMapHashSet,如果你没有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检查second的hashCode是否与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,则它必须存在于Bucket 1中。 所以只会搜索Bucket 1 !!

答案 9 :(得分:6)

为了使用我们自己的类对象作为HashMap,Hashtable等集合中的键,我们应该通过了解集合的内部工作来覆盖这两个方法(hashCode()和equals())。否则,它会导致我们不期望的错误结果。

答案 10 :(得分:5)

为什么我们要覆盖 equals() 方法

在 Java 中,我们不能重载 ==、+=、-+ 等运算符的行为方式。他们以某种方式行事。因此,让我们在这里重点关注运算符 ==。

运算符 == 的工作原理。

它检查我们比较的 2 个引用是否指向内存中的同一个实例。仅当这 2 个引用代表内存中的同一实例时,运算符 == 才会解析为 true。

那么现在让我们考虑以下示例

public class Person {

      private Integer age;
      private String name;
    
      ..getters, setters, constructors
      }

假设在您的程序中,您在不同的地方构建了 2 个 Person 对象,并且您希望对它们进行比较。

Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 );  --> will print false!

从业务角度来看,这两个对象看起来一样吧?对于 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);
    }

让我们回到之前的例子

    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!

所以我们不能重载 == 操作符来按照我们想要的方式比较对象,但是 Java 给了我们另一种方式,equals() 方法,我们可以根据需要覆盖它。

请记住但是,如果我们不在我们的类中提供自定义版本的 .equals()(又名覆盖),那么来自 Object 类的预定义 .equals() 和 { {1}} 运算符的行为完全相同。

继承自 Object 的默认 == 方法将检查两个比较的实例在内存中是否相同!

为什么我们要覆盖 equals() 方法

Java 中的一些数据结构,如 HashSet、HashMap,基于应用于这些元素的哈希函数来存储它们的元素。散列函数是 hashCode()

如果我们可以选择覆盖 hashCode() 方法,那么我们也必须选择覆盖 .equals() 方法。这是有原因的。

从 Object 继承的 hashCode() 的默认实现认为内存中的所有对象都是唯一的!

让我们回到那些散列数据结构。这些数据结构有一个规则。

HashSet 不能包含重复值,HashMap 不能包含重复键

HashSet 在幕后使用 HashMap 实现,其中 HashSet 的每个值都作为键存储在 HashMap 中。

所以我们必须了解 HashMap 是如何工作的。

简单来说,HashMap 是一个具有一些桶的本机数组。每个桶都有一个链表。在那个链表中,我们的密钥被存储。 HashMap 通过应用 hashCode() 方法为每个键定位正确的链表,然后它遍历该链表的所有元素并对这些元素中的每一个应用 hashCode() 方法以检查该元素是否已经包含在那里。不允许重复键。

enter image description here

当我们将某些东西放入 HashMap 时,键将存储在这些链表之一中。该键将存储在哪个链表中由该键上的 equals() 方法的结果显示。因此,如果 hashCode() 的结果为 4,那么 key1 将存储在数组的第 4 个存储桶中,即存在于那里的链表中。

默认情况下,key1.hashCode() 方法为每个不同的实例返回不同的结果。如果我们有默认的 hashCode(),它的行为类似于 ==,它将内存中的所有实例视为不同的对象,我们没有任何问题。

但在我们之前的示例中,我们说过如果年龄和姓名匹配,我们希望 Person 实例被视为相等。

equals()

现在让我们创建一个映射,将这些实例存储为键,一些字符串作为对值

    Person person1 = new Person("Mike", 34);
    Person person2 = new Person("Mike", 34);
    System.out.println ( person1.equals(person2) );  --> will print true!

在 Person 类中,我们没有覆盖 Map<Person, String> map = new HashMap(); map.put(person1, "1"); map.put(person2, "2"); 方法,但我们覆盖了 hashCode 方法。由于默认的 equals 为不同的 java 实例提供不同的结果,hashCodeperson1.hashCode() 有很大的机会得到不同的结果。

我们的地图可能以不同链表中的那些人结尾。

enter image description here

这违背了 HashMap 的逻辑

一个 HashMap 不允许有多个相同的键!

但我们现在有了,原因是从对象类继承的默认 person2.hashCode() 是不够的。不是在我们覆盖 Person 类上的 hashCode() 方法之后。

这就是为什么我们必须在覆盖 equals() 方法后覆盖 hashCode() 方法的原因。

现在让我们解决这个问题。让我们重写我们的 equals 方法以考虑 hashCode() 考虑的相同字段,即 equals()

age, name

现在让我们再次尝试将这些键保存在我们的 HashMap 中

 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);
    }

      }

Map<Person, String> map = new HashMap(); map.put(person1, "1"); map.put(person2, "2"); person1.hashCode() 肯定是一样的。假设它是 0。

HashMap 将转到存储桶 0,在该 LinkedList 中,将 person1 保存为值为“1”的键。对于第二个 put HashMap 足够智能,当它再次进入存储桶 0 以保存值为“2”的 person2 键时,它将看到另一个相等的键已经存在于那里。所以它会覆盖以前的密钥。所以最终我们的 HashMap 中只会存在 person2 键。

enter image description here

现在我们符合 Hash Map 的规则,即不允许多个相等的键!

答案 11 :(得分:5)

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()在条目
  • 中查找值

如果两者都被覆盖,

地图&LT;的 A &GT;

Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...

如果没有覆盖等于

地图&LT;的 A &GT;

Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..

如果没有覆盖hashCode

地图&LT;的 A &GT;

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 Contract

  1. 根据相等方法相等的两个键应该生成相同的hashCode
  2. 生成相同hashCode的两个密钥不必相等(在上面的示例中,所有偶数都生成相同的哈希代码)

答案 12 :(得分:5)

Java提出了

规则
  

“如果两个对象使用Object类equals方法相等,那么hashcode方法应该为这两个对象赋予相同的值。”

因此,如果在我们的课程中我们覆盖equals(),我们应该覆盖hashcode()方法也遵循此规则。 例如,在equals()中使用hashcode()Hashtable两种方法将值存储为键值对。如果我们覆盖一个而不是另一个,如果我们使用这样的对象作为密钥,Hashtable可能无法正常工作。

答案 13 :(得分:4)

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()之间的合同是:

  1. 如果两个对象相等,则它们必须具有相同的哈希码。
  2. 如果两个对象具有相同的哈希码,则它们可能相等也可能不相等。

    public int hashCode(){  
      return this.color.hashCode(); 
    }
    

答案 14 :(得分:4)

我正在查看解释“如果你只覆盖hashCode,那么当你调用myMap.put(first,someValue)时它首先需要,计算它的hashCode并将其存储在给定的存储桶中。然后当你调用myMap.put(first,someOtherValue)它应该根据地图文档首先替换第二个,因为它们是相同的(根据我们的定义)。“ :

我认为第二次添加myMap时,它应该是{第二个'对象,如myMap.put(second,someOtherValue)

答案 15 :(得分:4)

考虑在桶中收集全部为黑色的球。你的工作是如下为这些球着色并将其用于适当的游戏,

对于网球 - 黄色,红色。 对于Cricket - White

现在铲斗有黄色,红色和白色三种颜色的球。现在你做了着色只有你知道哪种颜色适合哪种游戏。

着色球 - 哈希。 为比赛选择球 - 等于。

如果你做过着色并且有人选择球进行板球或网球,他们不会介意颜色!

答案 16 :(得分:3)

假设你有一个聚合另外两个(B)(C)的类(A),你需要在哈希表中存储(A)的实例。默认实现仅允许区分实例,但不允许区分(B)和(C)。因此,A的两个实例可能相等,但默认情况下不允许您以正确的方式比较它们。

答案 17 :(得分:3)

Java中的Equals和Hashcode方法

它们是java.lang.Object类的方法,它是所有类的超类(自定义类以及java API中定义的其他类)。

<强>实施

  

public boolean equals(Object obj)

     

public int hashCode()

enter image description here

public boolean equals(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,它是一致的:,x.equals(y)的多次调用始终返回true或始终返回false,前提是在对象的equals比较中没有使用的信息是修改。

  

对于任何非空引用值x,应返回x.equals(null)   假的。

public int hashCode()

此方法返回调用此方法的对象的哈希码值。此方法将哈希码值作为整数返回,并且支持基于哈希的集合类(如Hashtable,HashMap,HashSet等)的优势。必须在覆盖equals方法的每个类中重写此方法。

hashCode的一般合约是:

每当在执行Java应用程序期间多次在同一对象上调用它时,hashCode方法必须始终返回相同的整数,前提是不修改对象的equals比较中使用的信息。

从应用程序的一次执行到同一应用程序的另一次执行,此整数不需要保持一致。

如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须产生相同的整数结果。

如果两个对象根据equals(java.lang.Object)方法不相等,则不需要在两个对象中的每一个上调用hashCode方法必须生成不同的整数结果。但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。

  

Equal对象必须生成相同的哈希码,只要它们是   等于,但是不对等的对象不需要产生不同的哈希码。

资源:

JavaRanch

Picture

答案 18 :(得分:3)

使用Value Objects时非常有用。以下摘自Portland Pattern Repository

  

价值对象的例子就是事物   喜欢数字,日期,钱和   字符串。通常,它们很小   广泛使用的对象。   他们的身份是基于他们的国家   而不是他们的对象身份。   这样,您就可以拥有多个副本   相同的概念价值对象。

     

所以我可以拥有多份副本   表示日期1月16日的对象   这些副本中的任何一个都是相同的。小的   像这样的对象,经常是   更容易创建新的和移动   他们身边而不是依靠   单个对象来表示日期。

     

值对象应始终覆盖   Java中的.equals()(或者在Smalltalk中)。   (请记住将.hashCode()覆盖为   好。)

答案 19 :(得分:3)

方法equals和hashcode在对象类中定义。默认情况下,如果equals方法返回true,则系统将进一步检查哈希码的值。如果2个对象的哈希码也相同,则对象将被视为相同。因此,如果仅覆盖equals方法,则即使重写的equals方法指示2个对象相等,系统定义的哈希码也可能不表示2个对象相等。所以我们也需要覆盖哈希码。

答案 20 :(得分:2)

在下面的示例中,如果在Person类中注释掉equals或hashcode的覆盖,则此代码将无法查找Tom的顺序。使用哈希码的默认实现可能会导致哈希表查找失败。

我在下面的内容是一个简化的代码,用于提升人们的订单。 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);
        }
    }
}

答案 21 :(得分:2)

String类和包装类与Object类具有不同的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的内部工作原则是Hashing。存在一些存储入口集的存储桶。您可以根据需要自定义hashCode()实现,以便可以将相同的类别对象存储到同一索引中。 当您使用put(k,v)方法将值存储到Map集合时,put()的内部实现是:

put(k, v){
hash(k);
index=hash & (n-1);
}

表示它生成索引,并且基于特定密钥对象的哈希码生成索引。因此,使此方法根据您的要求生成哈希码,因为相同的哈希码入口集将存储在同一个桶或索引中。

就是这样!

答案 22 :(得分:1)

恕我直言,根据规则说 - 如果两个对象相等,那么它们应该具有相同的哈希值,即相等的对象应该产生相等的哈希值。

如上所述,Object中的默认equals()是==,它对地址进行比较,hashCode()返回整数的地址(实际地址上的哈希值),这对于不同的Object来说又是不同的。

如果需要在基于散列的集合中使用自定义对象,则需要覆盖equals()和hashCode(),示例如果我想维护Employee对象的HashSet,如果我不这样做使用更强的hashCode和equals我可能会覆盖两个不同的Employee对象,当我使用age作为hashCode()时会发生这种情况,但是我应该使用可以作为Employee ID的唯一值。

答案 23 :(得分:1)

为了帮助您检查重复的对象,我们需要一个自定义的equals和hashCode。

由于hashcode总是返回一个数字,因此总是使用数字而不是字母键来快速检索对象。 它将如何做?假设我们通过传递一些其他对象中已有的值来创建一个新对象。现在,新对象将返回与另一个对象相同的哈希值,因为传递的值相同。一旦返回相同的哈希值,JVM将每次都转到相同的内存地址,如果对于相同的哈希值存在多个对象,它将使用equals()方法来识别正确的对象。

答案 24 :(得分:1)

如果您覆盖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

答案 25 :(得分:1)

hashCode()方法用于获取给定对象的唯一整数。当此对象需要存储在某些HashTableHashMap类数据结构中时,此整数用于确定存储桶位置。默认情况下,Object的hashCode()方法返回存储对象的内存地址的整数表示。

当我们将对象插入hashCode()HashTableHashMap时,会使用HashSet对象方法。有关Wikipedia.org上的HashTables的更多信息,以供参考。

要在地图数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义数据类型,则键的hashCode()将确定内部存储对象的位置。当需要从地图中查找对象时,密钥的哈希码将决定在哪里搜索对象。

哈希码仅在内部指向某个“区域”(或列表,存储桶等)。由于不同的密钥对象可能具有相同的哈希码,因此哈希码本身并不能保证找到正确的密钥。 HashTable然后迭代此区域(具有相同哈希码的所有键)并使用密钥的equals()方法来查找正确的密钥。找到正确的密钥后,将返回为该密钥存储的对象。

因此,正如我们所看到的,在hashCode()中存储和查找对象时,会使用equals()HashTable方法的组合。

注意:

  1. 始终使用对象的相同属性来生成hashCode()equals()两者。在我们的案例中,我们使用了员工ID。

  2. equals()必须一致(如果未修改对象,则必须保持返回相同的值)。

  3. 每当a.equals(b)a.hashCode()必须与b.hashCode()相同时。

  4. 如果你覆盖一个,那么你应该覆盖另一个。

  5. http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html

答案 26 :(得分:0)

当要在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,因为它们相等。

答案 27 :(得分:0)

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)用于比较地址比较,这就是为什么在Test类中,如果您比较两个对象,然后equals方法给出false,但是当我们重写hashcode()时,它可以比较内容并给出正确的结果。

答案 28 :(得分:-1)

这两种方法都在Object类中定义。两者都是最简单的实现。因此,当您需要为这些方法添加更多实现时,您可以在类中进行覆盖。

对于Ex:对象中的equals()方法仅检查其在引用上的相等性。因此,如果您还需要比较其状态,那么您可以覆盖它,就像在String类中完成一样。

答案 29 :(得分:-4)

Bah - &#34;您必须覆盖覆盖equals()的每个类中的hashCode()。&#34;

[来自Effective Java,Joshua Bloch?]

这不是错误的方式吗?重写hashCode可能意味着你正在编写一个哈希键类,但是覆盖equals肯定不会。有许多类不用作哈希键,但出于某些其他原因需要逻辑相等测试方法。如果你选择&#34;等于&#34;对于它,您可能会被要求通过过度应用此规则来编写hashCode实现。所有这一切都是在代码库中添加未经测试的代码,这是一种等待将来绊倒某人的邪恶。编写你不需要的代码也是反敏捷的。这是错的(并且生成的ide可能与你手工制作的equals不兼容)。

当然他们应该在写入的对象上强制使用接口作为键吗?无论如何,Object永远不应该提供默认的hashCode()和equals()imho。它可能鼓励了许多破碎的哈希集合。

但无论如何,我认为&#34;规则&#34;写回到前面。与此同时,我将继续避免使用&#34; equals&#34;对于平等测试方法: - (