ChatGPT解决这个技术问题 Extra ChatGPT

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

最近我通读了这个 Developer Works Document

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

我怎样才能决定有效地实施这些方法?

案例覆盖仅等于:两个相同的对象将具有不同的哈希码 = 相同的对象进入不同的存储桶(重复)。案例覆盖仅哈希码:两个相同的对象将具有相同的哈希码 = 相同的对象进入同一个桶(重复)。
该链接似乎已失效。我可以获得 IBM 的开发人员工作文档吗?

S
SebastianWilke

Joshua Bloch 谈有效的 Java

您必须在覆盖 equals() 的每个类中覆盖 hashCode()。不这样做将导致违反 Object.hashCode() 的一般约定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。

让我们通过一个示例来理解它,如果我们覆盖 equals() 而不覆盖 hashCode() 并尝试使用 Map 会发生什么。

假设我们有一个这样的类,如果 importantField 相等,则 MyClass 的两个对象相等(eclipse 生成 hashCode()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;
    }

    @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

如果只有 equals 被覆盖,那么当您首先调用 myMap.put(first,someValue) 时,将散列到某个存储桶,当您调用 myMap.put(second,someOtherValue) 时,它将散列到其他存储桶(因为它们有不同的 hashCode)。因此,尽管它们是相等的,但由于它们没有散列到同一个桶,因此映射无法实现,它们都留在映射中。

虽然如果我们覆盖 hashCode() 就没有必要覆盖 equals(),让我们看看在这种特殊情况下会发生什么,我们知道 MyClass 的两个对象如果它们的 importantField 相等但我们不覆盖它们是相等的equals()

仅覆盖 hashCode

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

但问题是 equals 没有重新定义,所以当映射散列 second 并遍历存储桶查看是否有对象 k 使得 second.equals(k) 为真时,它不会找到任何对象,因为 second.equals(first) 会是false

希望很清楚


您能否详细说明一下,在第二种情况下,为什么第二个对象必须放在另一个桶中?
我不喜欢这个答案,因为它表明你不能在不覆盖 equals() 的情况下覆盖 hashCode(),这根本不是真的。您说您的示例代码(“仅覆盖 hashCode”部分)不起作用,因为您将两个对象定义为相等,但是 - 抱歉 - 这个定义只是在你的脑海中。在您的第一个示例中,您有两个具有相同 hashCode 的不相等对象,这是完全合法的。所以你需要重写equals()的原因不是因为你已经重写了hashCode(),而是因为你想把你的“等于”定义从你的头脑中移到代码中。
if you think you need to override one, then you need to override both of them 是错误的。如果您的类覆盖 equals 但反向不正确,则需要覆盖 hashCode
我认为完全只覆盖 hashCode() 而不覆盖 equals() 是完全可以的。它也是用 Effective Java 编写的:books.google.fr/…
@PhantomReference,请注意,仅覆盖 equals 会违反 Object 的 javadoc 中规定的约定:“如果根据 equals(Object) 方法两个对象相等,则在每个对象上调用 hashCode 方法两个对象中的一个必须产生相同的整数结果。” 当然,并非所有合约的所有部分都在所有代码中执行,但从形式上讲,这是一种违规行为,我认为这是一个等待发生。
r
rajeev pani..

HashMapHashSet 等集合使用对象的 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

现在取消注释 hashcode() 方法,执行相同的操作,输出将是:

true
employee.hashCode():  -938387308  employee2.hashCode():-938387308

现在你能明白为什么如果两个对象被认为相等,那么它们的 hashcode 也必须相等吗?否则,您将永远无法找到该对象,因为 Object 类中的默认 hashcode 方法实际上总是为每个对象提供一个唯一编号,即使 equals() 方法在此类中被覆盖一种将两个或多个对象视为相等的方式。如果对象的 hashcode 没有反映这一点,那么对象的平等程度并不重要。所以再来一次:如果两个对象相等,那么它们的 hashcode 也必须相等。


@rajeev我有一个困惑,当我们在HashMap的情况下覆盖hashCode方法时,为什么我们需要覆盖equals方法?在任何情况下,如果对象的哈希码相等,则 hashmap 会替换该值。
@VikasVerma equals 对象将具有相等的哈希码并不意味着不相等的对象将具有不相等的哈希码。如果对象实际上不同,但它们的哈希码相同怎么办?
有什么理由在 hashcode() 实现中使用这些数字(17 和 31)来生成哈希码?我们可以使用任何随机数吗?
@JavaYouth 是的,你可以
@JavaYouth ,正如 Rajeev 所提到的,您可以使用任何其他值。但是,建议使用素数,因为这样会产生更少的冲突。
l
likejudo

您必须在覆盖 equals() 的每个类中覆盖 hashCode()。不这样做将导致违反 Object.hashCode() 的一般约定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。来自 Effective Java,作者 Joshua Bloch

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

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


这是正确答案。当然,如果您从不在基于哈希的集合中使用该类,那么您没有实现 hashCode() 也没关系。
在更复杂的情况下,您永远不知道您使用的集合是否使用哈希,因此请远离“您没有实现 hashCode() 并不重要”
我可以在不覆盖 equals() 的情况下覆盖 hashCode() 吗?
@Johnny当然你可以覆盖hascode而不覆盖equals。但是用例会是什么?
@Gi1ber7 从这里稍微检查一下我的答案,以分析了解 equalshashCode 的 HashMap 和 HashTable 发生了什么
P
Premraj

身份不是平等。

等于运算符 == 测试身份。

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 方法。

不相等的实例可能具有相同的哈希码。

相等的实例应该返回相同的哈希码。


P
Panagiotis Bougioukos

我们为什么要覆盖 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() 将完全按照相同的。

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

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

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

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

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

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

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

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

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

简单来说,HashMap 是一个包含一些桶的原生数组。每个桶都有一个linkedList。在该linkedList 中存储了我们的密钥。 HashMap 通过应用 hashCode() 方法为每个键定位正确的linkedList,然后遍历该linkedList 的所有元素,并对每个元素应用equals() 方法以检查该元素是否已包含在其中。不允许有重复的键。

https://i.stack.imgur.com/aMwel.png

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

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

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

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

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

Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");

在 Person 类中,我们没有覆盖 hashCode 方法,但我们已经覆盖了 equals 方法。由于默认的 hashCode 为不同的 java 实例提供了不同的结果,因此 person1.hashCode()person2.hashCode() 很有可能产生不同的结果。

我们的地图可能以不同链接列表中的人结束。

https://i.stack.imgur.com/TOSNY.png

这违背了 HashMap 的逻辑

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

但是我们现在有,原因是从 Object Class 继承的默认 hashCode() 还不够。不是在我们覆盖了 Person 类的 equals() 方法之后。

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

现在让我们解决这个问题。让我们覆盖 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);
    }

      }

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

Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");

person1.hashCode()person2.hashCode() 肯定是一样的。假设它是0。

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

https://i.stack.imgur.com/i7hek.png

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


关于..getters, setters, constructors的关注点。您在 Objects.hash(name, age) 中使用的字段应始终为 final。否则对象的哈希码可能会改变,这将导致 Set/Map 和其他的很多问题。请参阅docs.oracle.com/javase/6/docs/api/java/lang/…
S
Shashi

简而言之,Object 中的 equals 方法检查引用是否相等,因为当属性相等时,类的两个实例在语义上仍然可能相等。例如,在将对象放入使用 equals 和哈希码的容器(如 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 的对象是同一个对象,无论它是否是两个不同的实例。我们覆盖等号(和哈希码):

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's helper methods


L
Lii

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

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

要理解为什么我们必须重写 equals 和 hashcode 方法,如果需要首先了解什么是 hashmap 和什么是做什么的。

hashmap 是一种数据结构,它以数组方式存储数据的键值对。比方说 a[],其中 'a' 中的每个元素都是一个键值对。

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

现在为什么要使用哈希图?

如果我们必须在一个大数组中搜索,那么搜索每个数组是否效率不高,那么哈希技术告诉我们让我们用一些逻辑预处理数组并根据该逻辑对元素进行分组,即散列

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

现在除了上面的 hashmap 还告诉它不会在其中添加任何 Duplicates。这就是我们必须覆盖equals和hashcode的主要原因

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

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

所以 put 的作用是它首先为给定键生成哈希码,以决定该值应该进入哪个索引。如果该索引处没有任何内容,那么如果那里已经存在某些东西,那么新值将被添加到那里然后应该在该索引处的链表末尾添加新值。但请记住,不应根据哈希图的所需行为添加重复项。所以假设你有两个整数对象 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;
        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());
    }
}

我有一个困惑,为什么我们需要在 HashMap 的情况下覆盖 hashCode 方法时覆盖 equals 方法?在任何情况下,如果对象的哈希码相等,则 hashmap 会替换该值。
如果对象的哈希码相等,@VikasVerma 哈希图不会替换任何类型的值,它只决定必须将新添加到哈希图中的对象放置在哪里的索引。现在可以在索引处有对象,因此为了避免重复,我们重写了 equals 方法,并编写了用于定义何时将比较的两个对象视为相等的逻辑。如果没有被覆盖,那么尽管具有相同值的对象将被存储,因为两个对象的引用将不同
N
Natasha Kurian

hashCode()

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

equals()

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

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

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


这是有趣的一点,关于仅覆盖 hashCode()。完全没问题,对吧?或者也可能有问题的情况?
这是一个误导和错误的答案。覆盖(=only=)hashCode() 确保被实例化的具有相似属性的相应类的每个对象都具有相同的哈希码。但不会有用,因为它们都不相等。
R
Rany Albeg Wein

Java 提出了一个规则:

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

因此,如果在我们的类中重写 equals(),我们也应该重写 hashcode() 方法以遵循此规则。 equals()hashcode() 这两种方法都在 Hashtable 中使用,例如,将值存储为键值对。如果我们覆盖一个而不是另一个,如果我们使用这样的对象作为键,Hashtable 可能无法按我们的意愿工作。


P
PaulJWilliams

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

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


P
Prashanth

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


u
user104309

添加到@Lombo 的答案

您什么时候需要覆盖 equals() ?

Object 的 equals() 的默认实现是

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

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

但是,如果两个对象的一个或多个属性具有相同的值,您可能希望将它们视为相同(请参阅@Lombo 的答案中给出的示例)。

因此,您将在这些情况下覆盖 equals(),并给出自己的相等条件。

我已经成功实现了 equals() 并且效果很好。那么为什么他们也要求覆盖 hashCode() 呢?

好吧。只要您不在用户定义的类上使用 “Hash” based Collections,就可以了。但在未来的某个时候,您可能想要使用 HashMapHashSet,如果您不 override 并且 “正确实现”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 中。因此只会搜索 Bucket 1!


b
bharanitharan
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() 查找 Entry(桶),然后使用 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 等价合约

根据相等方法相等的两个键应生成相同的 hashCode 生成相同 hashCode 的两个键不必相等(在上面的示例中,所有偶数都生成相同的 hash Code)


S
Suraj Rao

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


r
reevesy

使用 Value Objects 时很有用。以下是 Portland Pattern Repository 的摘录:

值对象的例子有数字、日期、货币和字符串。通常,它们是被广泛使用的小物体。他们的身份基于他们的状态而不是他们的对象身份。这样,您可以拥有同一个概念值对象的多个副本。因此,我可以拥有代表 1998 年 1 月 16 日的对象的多个副本。这些副本中的任何一个都将彼此相等。对于像这样的小对象,通常更容易创建新对象并移动它们,而不是依赖单个对象来表示日期。值对象应始终覆盖 Java 中的 .equals()(或 Smalltalk 中的 =)。 (记住也要覆盖 .hashCode() 。)


D
Dewfy

假设您有聚合两个其他 (B) (C) 的类 (A),并且您需要将 (A) 的实例存储在哈希表中。默认实现仅允许区分实例,但不允许区分 (B) 和 (C)。所以 A 的两个实例可能相等,但默认不允许您以正确的方式比较它们。


b
bharanitharan

考虑在桶中收集所有黑色的球。你的工作是给这些球上色如下,并将其用于适当的游戏,

网球 - 黄色,红色。板球 - 白色

现在水桶有黄色、红色和白色三种颜色的球。现在你做了着色只有你知道哪种颜色适用于哪个游戏。

给球上色 - 散列。为比赛选择球 - 等于。

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


A
Aakash Goplani

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

我认为当我们第二次添加 myMap 时,它应该是像 myMap.put(second,someOtherValue) 这样的“第二个”对象


A
Aarti

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


如果 equals 方法返回 true,则无需检查哈希码。但是,如果两个对象具有不同的哈希码,则应该能够将它们视为不同的,而不必调用 equals。此外,知道列表中的任何事物都没有特定的哈希码意味着列表中的任何事物都不能与具有该哈希码的任何对象匹配。举个简单的例子,如果有一个哈希码为偶数的对象列表,以及一个奇数对象列表,则哈希码为偶数的对象将不会出现在第二个列表中。
如果有两个对象 X 和 Y,它们的“等于”方法表明它们匹配,但是 X 的哈希码是偶数,Y 的哈希码是奇数,如上所述的集合,它指出对象 Y 的哈希码是奇数并存储它在第二个列表中将无法找到对象 X 的匹配项。它会观察到 X 的哈希码是偶数,并且由于第二个列表没有任何具有偶数哈希码的对象,它不会打扰在那里搜索匹配 X 的东西,即使 Y 会匹配 X。你应该说什么......
...会是许多集合将避免比较哈希码暗示它们不相等的事物。给定两个哈希码未知的对象,直接比较它们通常比计算它们的哈希码更快,因此不能保证报告不相等哈希码但为 equals 返回 true 的事物不会被视为匹配。另一方面,如果集合注意到事物不能具有相同的哈希码,它们可能不会注意到它们是相等的。
A
Aftab

Java中的Equals和Hashcode方法

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

执行:

public boolean equals(Object obj) public int hashCode()

https://i.stack.imgur.com/0aaFt.jpg

公共布尔等于(对象 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) 应该返回 false。

公共 int hashCode()

此方法返回调用此方法的对象的哈希码值。此方法以整数形式返回哈希码值,并支持基于哈希的集合类,如 Hashtable、HashMap、HashSet 等。必须在每个覆盖 equals 方法的类中覆盖此方法。

hashCode 的一般合约是:

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

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

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

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

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

资源:

JavaRanch

Picture


图片(视频链接)处于私密模式。公开观看。
9
91StarSky

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

d
developer747

在下面的示例中,如果您在 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);
        }
    }
}

A
Aakash Goplani

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

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

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

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

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

笔记:

始终使用对象的相同属性来生成 hashCode() 和 equals() 两者。在我们的例子中,我们使用了员工 ID。 equals() 必须是一致的(如果对象没有被修改,那么它必须保持返回相同的值)。每当 a.equals(b) 时,a.hashCode() 必须与 b.hashCode() 相同。如果你覆盖一个,那么你应该覆盖另一个。

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


hashCode() 不用于为每个对象返回唯一的整数。那是不可能的。你在第四段的第二句中自己反驳了这一点。
@EJP,大多数时候 hascode() 将返回两个不同对象的唯一整数。但是对于两个不同的对象,hascode 是有可能发生碰撞的,这个概念被称为 Hashcode Collision。请参考:tech.queryhome.com/96931/…
A
Arun Raaj

String 类和包装类具有与 Object 类不同的 equals()hashCode() 方法实现。 Object 类的 equals() 方法比较对象的引用,而不是内容。 Object 类的 hashCode() 方法为每个对象返回不同的哈希码,无论内容是否相同。

当您使用 Map 集合并且 key 是 Persistent 类型,StringBuffer/builder 类型时,它会导致问题。由于它们不像 String 类那样覆盖 equals() 和 hashCode(),所以当您比较两个不同的对象时,即使它们具有相同的内容,equals() 也会返回 false。它将使 hashMap 存储相同的内容键。存储相同的内容键意味着它违反了 Map 的规则,因为 Map 根本不允许重复键。因此,您在类中重写 equals() 和 hashCode() 方法并提供实现(IDE 可以生成这些方法),以便它们与 String 的 equals() 和 hashCode() 工作相同,并防止相同的内容键。

您必须与 equals() 一起覆盖 hashCode() 方法,因为 equals() 根据哈希码工作。

此外,重写 hashCode() 方法和 equals() 有助于完整地完成 equals()-hashCode() 合约:“如果两个对象相等,那么它们必须具有相同的哈希码。”

什么时候需要为 hashCode() 编写自定义实现?

众所周知,HashMap的内部工作是基于Hashing原理的。有一些存储条目集的存储桶。您可以根据需要自定义 hashCode() 实现,以便可以将相同的类别对象存储到相同的索引中。当您使用 put(k,v) 方法将值存储到 Map 集合中时,put() 的内部实现是:

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

意思是,它生成索引,索引是根据特定关键对象的哈希码生成的。因此,请让此方法根据您的要求生成哈希码,因为相同的哈希码条目集将存储到相同的存储桶或索引中。

而已!


C
Cleonjoys

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

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

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


T
Tavash

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

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


A
Ambrish Rajput

当您想在 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,因为它们是相等的。


S
Suraj Rao
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(Object obj) 用于比较地址比较,这就是为什么在测试类中,如果您比较两个对象,然后 equals 方法给出 false,但是当我们覆盖 hashcode() 时,它可以比较内容并给出正确的结果。
您可以使用此答案下方的编辑链接添加到您的答案中。请不要将答案添加为两个不完整的答案
S
Shashi

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

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


s
steve

这个答案中没有提到测试 equals/hashcode 合约。

我发现 EqualsVerifier 库非常有用且全面。它也很容易使用。

此外,从头开始构建 equals()hashCode() 方法涉及大量样板代码。 Apache Commons Lang 库提供 EqualsBuilderHashCodeBuilder 类。这些类极大地简化了复杂类的 equals()hashCode() 方法的实现。

顺便说一句,值得考虑覆盖 toString() 方法以帮助调试。 Apache Commons Lang 库提供了 ToStringBuilder 类来帮助解决此问题。