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

Posted

技术标签:

【中文标题】为啥我需要覆盖 Java 中的 equals 和 hashCode 方法?【英文标题】:Why do I need to override the equals and hashCode methods in Java?为什么我需要覆盖 Java 中的 equals 和 hashCode 方法? 【发布时间】:2022-01-03 03:47:55 【问题描述】:

最近我通读了这个 Developer Works Document。

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

我如何才能做出有效实施这些方法的决定?

【问题讨论】:

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

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

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

【讨论】:

【参考方案2】:

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

【讨论】:

【参考方案3】:

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

值对象的例子是事物 比如数字、日期、金钱和 字符串。通常,它们很小 使用相当广泛的对象。 他们的身份是基于他们的状态 而不是他们的对象身份。 这样,您可以拥有多个副本 相同的概念值对象。

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

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

【讨论】:

【参考方案4】:

您必须在每个 覆盖 equals() 的类。失败 这样做将导致违反 总合同 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。但是用例会是什么?【参考方案5】:

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

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

【讨论】:

【参考方案6】:

简单地说,Object 中的 equals 方法检查引用是否相等,因为当属性相等时,您的类的两个实例在语义上仍然可能相等。例如,在将对象放入使用 equals 和哈希码的容器中时,这一点很重要,例如 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");

没有覆盖我们得到的等于:

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

至于实现 equals 和 hashcode 我可以推荐使用Guava's helper methods

【讨论】:

【参考方案7】:

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) 为真时,它不会找到任何 @ 987654350@ 将是false

希望清楚

【讨论】:

你能否详细说明一下,在第二种情况下,为什么第二个对象必须放在另一个桶中? 我不喜欢这个答案,因为它表明你不能在不覆盖 equals() 的情况下覆盖 hashCode(),这根本不是真的。您说您的示例代码(“仅覆盖 hashCode”部分)不起作用,因为您将两个对象定义为相等,但是 - 抱歉 - 这个定义只是在你的脑海中。在您的第一个示例中,您有两个具有相同 hashCode 的不相等对象,这是完全合法的。所以你需要重写equals()的原因不是因为你已经重写了hashCode(),而是因为你想把你的“equals”定义从你的头脑中移到代码中。 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 方法必须产生相同的整数结果。” 当然,并非所有合约的所有部分都在所有代码中执行,但从形式上讲,这是一种违规行为,我会认为这是一个等待发生的错误。【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

【讨论】:

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

hashCode()

如果你只重写 hash-code 方法,什么都不会发生,因为它总是为每个对象返回一个新的 hashCode 作为 Object 类。

equals()

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

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

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

【讨论】:

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

Java 制定了一个规则

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

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

【讨论】:

【参考方案12】:

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

【讨论】:

【参考方案13】:

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

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

要在地图数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义的数据类型,则键的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() 不用于为每个对象返回唯一的整数。那是不可能的。你自己在第四段的第二句中反驳了这一点。 @EJP,大多数时候 hascode() 将返回两个不同对象的唯一整数。但是对于两个不同的对象,hascode 是有可能发生碰撞的,这个概念被称为 Hashcode Collision。请参考:tech.queryhome.com/96931/…【参考方案14】:

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 没有反映这一点。再来一次:如果两个对象相等,它们的 hashcodes 也必须相等。

【讨论】:

@rajeev 我有一个困惑,为什么我们需要在 HashMap 的情况下重写 hashCode 方法时重写 equals 方法?在任何情况下,如果对象的哈希码相等,则 hashmap 会替换该值。 @VikasVerma equals 对象将具有相等的哈希码并不意味着不相等的对象将具有不相等的哈希码。如果对象实际上不同,但它们的哈希码相同怎么办? 即使我们注释了equals方法并取消注释hashcode方法,那么它也是错误的,因为即使找到了正确的桶(使用哈希码)但没有找到正确的元素。输出 :: false employee.hashCode(): -938387308 employee2.hashCode():-938387308 有什么理由在 hashcode() 实现中使用这些数字(17 和 31)来生成哈希码?我们可以使用任何随机数吗? @JavaYouth 是的,你可以【参考方案15】:

添加到@Lombo 的答案

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

Object的equals()的默认实现是

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

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

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

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

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

好吧。只要你不在你的用户定义的类上使用“Hash” based Collections,就可以了。 但是在未来的某个时候,您可能想要使用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相同。 只有当值相同时,才会继续检查同一个桶中的相等性。

但是这里这 2 个对象的 hashCode 是不同的(因为它们具有不同的内存地址 - 来自默认实现)。 因此,它甚至不会检查是否相等。

如果你在重写的 equals() 方法中有一个断点,如果它们有不同的 hashCode,它就不会介入。 contains() 检查 hashCode() 并且只有当它们相同时才会调用您的 equals() 方法。

为什么我们不能让 HashMap 检查所有桶中的相等性?所以我没有必要重写 hashCode() !!

那么您就错过了基于哈希的集合的要点。 考虑以下几点:

Your hashCode() implementation : intObject%9.

以下是以桶的形式存储的key。

Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...

说,你想知道地图是否包含键 10。 你想搜索所有的桶吗?还是只搜索一个存储桶?

根据 hashCode,您可以确定如果存在 10,则它必须存在于 Bucket 1 中。 所以只有 Bucket 1 会被搜索到!!

【讨论】:

【参考方案16】:

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

首先从更广泛的角度来看,我们有集合,而 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 的作用是首先为给定键生成哈希码,以决定该值应该进入哪个索引。如果该索引处没有任何内容,那么新值将被添加到那里,如果某些东西已经存在存在那里,那么应该在该索引处的链表末尾添加新值。但请记住,不应根据哈希图的所需行为添加重复项。所以假设你有两个 Integer 对象 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 hashmap 不会在对象的 hashcode 相等的情况下替换任何类型的值,它只决定必须将新添加到 hashmap 的对象放置的索引。现在可以在索引处有对象,因此为了避免重复,我们重写了 equals 方法,并编写了用于定义何时将比较的两个对象视为相等的逻辑。如果没有被覆盖,那么尽管具有相同值的对象将被存储,因为两个对象的引用将不同【参考方案17】:

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

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

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

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

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

【讨论】:

【参考方案18】:
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() 计算哈希值以确定 bucket 并使用equals() 方法查找该值是否已经存在 存在于桶中。如果不是,它将添加,否则将替换为当前值 get('key') 将首先使用hashCode() 查找条目(存储桶),然后 equals()在Entry中查找值

如果两者都被覆盖,

地图A>

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

如果等于没有被覆盖

地图A>

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

如果 hashCode 没有被覆盖

地图A>

Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...

HashCode 等价合约

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

【讨论】:

【参考方案19】:

身份不是平等的。

等于运算符== 测试身份。 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 生成整数以便将对象存储在 HashMapHashSet 等数据结构中。

假设我们如上覆盖Customer的equals方法,

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

当我们在桶中存储对象时使用数据结构(桶是文件夹的一个花哨的名称)。如果我们使用内置哈希技术,对于以上两个客户,它会生成两个不同的哈希码。所以我们将同一个相同的对象存储在两个不同的地方。为避免此类问题,我们还应基于以下原则重写 hashCode 方法。

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

【讨论】:

这是我从过去 1 小时以来一直在寻找的。很棒的伙伴(y)【参考方案20】:

呸 - “您必须在每个覆盖 equals() 的类中覆盖 hashCode()。”

[来自 Effective Java,作者 Joshua Bloch?]

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

他们当然应该在编写用作键的对象上规定一个接口?无论如何,Object 永远不应该提供默认的 hashCode() 和 equals() 恕我直言。这可能鼓励了许多损坏的哈希集合。

但无论如何,我认为“规则”是从头到尾写的。同时,我将继续避免使用“equals”来进行相等性测试方法:-(

【讨论】:

【参考方案21】:

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

【讨论】:

【参考方案22】:

Java 中的 Equals 和 Hashcode 方法

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

实施:

公共布尔等于(对象 obj)

public int hashCode()

公共布尔等于(对象 obj)

此方法只是检查两个对象引用 x 和 y 是否引用同一个对象。即它检查是否 x == y。

它是自反的: 对于任何参考值 x,x.equals(x) 应该返回 true。

它是对称的: 对于任何参考值 x 和 y,x.equals(y) 应该返回 true 当且仅当 y.equals(x) 返回 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(Object)方法相等,那么对两个对象中的每一个调用hashCode方法必须产生相同的整数结果。

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

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

资源:

JavaRanch

Picture

【讨论】:

图片(视频链接)处于私密模式。公开观看。【参考方案23】:

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

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

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

【讨论】:

【参考方案24】:

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

【讨论】:

【参考方案25】:

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)method 将值存储到 Map 集合中时,put() 的内部实现是:

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

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

就是这样!

【讨论】:

【参考方案26】:

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

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

【讨论】:

【参考方案27】:

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

【讨论】:

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

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

【讨论】:

【参考方案30】:

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

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

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

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

【讨论】:

以上是关于为啥我需要覆盖 Java 中的 equals 和 hashCode 方法?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我需要覆盖 C# 中的 .Equals 和 GetHashCode [重复]

为啥 StringBuffer/StringBuilder 不覆盖 equals 或 hashCode?

覆盖 Java 中的 hashCode() 和 equals() 方法

为啥 set.contains 不使用覆盖的 equals() 方法?

Java:Effective java学习笔记之 覆盖equals时请遵守通用约定

equals()和hashCode()必须同时覆盖的原因