📜  为什么要覆盖 equals(Object) 和 hashCode() 方法?

📅  最后修改于: 2022-05-13 01:55:20.029000             🧑  作者: Mango

为什么要覆盖 equals(Object) 和 hashCode() 方法?

先决条件 - Equals 和 Hashcode 方法
HashMap 和 HashSet 使用对象的哈希码值来找出对象将如何存储在集合中,随后哈希码用于帮助在集合中定位对象。哈希检索涉及:

  1. 首先,使用 hashCode() 找出正确的存储桶。
  2. 其次,使用 equals() 在桶中搜索正确的元素

让我们考虑这些方法中的所有覆盖情况

案例 1:覆盖 equals(Object) 和 hashCode() 方法

您必须在覆盖 equals() 的每个类中覆盖 hashCode()。不这样做将导致违反 Object.hashCode() 的一般约定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。 (——约书亚·布洛赫)
这是来自Java.lang.Object 专业化的合同:

  • 每当在Java应用程序执行期间对同一对象多次调用 it(hashcode) 时,hashCode 方法必须始终返回相同的整数,前提是没有修改对象上的 equals 比较中使用的信息。该整数不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。
  • 如果两个对象根据 equals(Object) 方法相等,则对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
  • 如果根据 equals(Java.lang.Object) 方法,如果两个对象不相等,则不需要对两个对象中的每一个调用 hashCode 方法都必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
// Java program to illustrate
// overriding of equals and
// hashcode methods
import java.io.*;
import java.util.*;
  
class Geek 
{
      
    String name;
    int id;
      
    Geek(String name, int id)
    {
          
        this.name = name;
        this.id = id;
    }
      
    @Override
    public boolean equals(Object obj)
    {
          
    // if both the object references are 
    // referring to the same object.
    if(this == obj)
            return true;
          
        // it checks if the argument is of the 
        // type Geek by comparing the classes 
        // of the passed argument and this object.
        // if(!(obj instanceof Geek)) return false; ---> avoid.
        if(obj == null || obj.getClass()!= this.getClass())
            return false;
          
        // type casting of the argument. 
        Geek geek = (Geek) obj;
          
        // comparing the state of argument with 
        // the state of 'this' Object.
        return (geek.name.equals(this.name)  && geek.id == this.id);
    }
      
    @Override
    public int hashCode()
    {
          
        // We are returning the Geek_id 
        // as a hashcode value.
        // we can also return some 
        // other calculated value or may
        // be memory address of the 
        // Object on which it is invoked. 
        // it depends on how you implement 
        // hashCode() method.
        return this.id;
    }
      
}
  
// Driver code
class GFG 
{
    public static void main (String[] args) 
    {
          
        // creating two Objects with 
        // same state
        Geek g1 = new Geek("aditya", 1);
        Geek g2 = new Geek("aditya", 1);
          
        Map map = new HashMap();
        map.put(g1, "CSE");
        map.put(g2, "IT");
          
        for(Geek geek : map.keySet())
        {
            System.out.println(map.get(geek).toString());
        }
  
    }
}

输出:

IT

在这种情况下,我们正确地覆盖了这两种方法。
当我们调用map.put(g1, “CSE”);它将散列到某个存储桶位置,当我们调用map.put(g2, “IT”); ,它将生成相同的哈希码值(与 g1 相同)并用第二个值替换第一个值,因为在迭代同一个桶时它发现 ak 使得 k.equals(g2) 为真,意味着搜索键已经存在。因此,它用新值替换了该键的旧值。

案例 2:仅覆盖 equals(Object) 方法

如果我们只覆盖 equals(Object) 方法,当我们调用map.put(g1, “CSE”);它将散列到某个存储桶位置,当我们调用map.put(g2, “IT”);由于 hashCode() 方法没有被覆盖,由于 hashcode 值不同,它会散列到其他存储桶位置。
hashcoe_1
正如您在图像中清楚地看到的那样,这两个值都存储到不同的存储桶位置。就像每次插入地图都会得到不同的桶位置,无论我们使用相同的关键对象还是不同的关键对象,即关键对象的状态是相同的还是不同的。

// Java program to illustrate
// Overriding only the equals(Object) method
import java.io.*;
import java.util.*;
  
class Geek 
{
    String name;
    int id;
       
    Geek(String name, int id)
    {
        this.name = name;
        this.id = id;
     }
       
    @Override
    public boolean equals(Object obj)
    {
       // if both the object references are 
       // referring to the same object.
       if(this == obj)
            return true;
            
        // it checks if the argument is of the 
        // type Geek by comparing the classes 
        // of the passed argument and this object.
        // if(!(obj instanceof Geek)) return false; ---> avoid.
        if(obj == null || obj.getClass()!= this.getClass())
            return false;
            
        // type casting of the argument.    
        Geek geek = (Geek) obj;
            
        // comparing the state of argument with 
        // the state of 'this' Object.
        return (geek.name.equals(this.name) && geek.id == this.id);
    }
}
  
class GFG 
{
    public static void main (String[] args) 
    {
          
        // creating two Objects with 
        // same state
        Geek g1 = new Geek("aditya", 1);
        Geek g2 = new Geek("aditya", 1);
          
        Map map = new HashMap();
        map.put(g1, "CSE");
        map.put(g2, "IT");
          
        for(Geek geek : map.keySet())
        {
            System.out.println(map.get(geek).toString());
        }
  
    }
}

输出:

CSE
IT

案例 3:仅覆盖 hashCode() 方法

考虑另一个 map 示例:

Map map = new HashMap();
map.put(“xyz”, “CSE”);
map.put(“xyz”, “IT”);

当我们调用map.put(“xyz”, “CSE”);它将生成哈希码值并将其存储到使用此地址(哈希码值)指定的存储桶位置。而当我们调用map.put(“xyz”, “IT”);它生成与前一个条目相同的哈希码值,因为键对象相同并且 hashCode() 方法已被覆盖。所以它应该按照规则用第二个替换第一个。但它没有。原因是,当它遍历该桶并试图找到 k 使得 k.equals(“xyz”) 即如果搜索键已经存在。但它找不到,因为 equals(Object) 方法尚未被覆盖。这是违反散列规则的。

// Java program to illustrate 
// Overriding only hashCode() method
  
import java.io.*;
import java.util.*;
  
class Geek 
{
    String name;
    int id;
       
    Geek(String name, int id)
    {
        this.name = name;
        this.id = id;
     }
      
    @Override
    public int hashCode()
    {
           
        // We are returning the Geek_id 
        // as a hashcode value.
        // we can also return some 
        // other calculated value or may
        // be memory address of the 
        // Object on which it is invoked. 
        // it depends on how you implement 
        // hashCode() method.
        return this.id;
    }
       
}
  
class GFG 
{
    public static void main (String[] args)
    {
          
        // creating two Objects with 
        // same state
        Geek g1 = new Geek("aditya", 1);
        Geek g2 = new Geek("aditya", 1);
          
        Map map = new HashMap();
        map.put(g1, "CSE");
        map.put(g2, "IT");
          
        for(Geek geek : map.keySet())
        {
            System.out.println(map.get(geek).toString());
        }
  
    }
}

输出:

CSE
IT

hashcode_3

在上图中,当我们调用map.put(“xyz”, “IT”);然后它尝试用第二个值(IT)替换第一个值(CSE),但这是不可能的,因此它将第二对(键,值)插入到哈希图内部使用的新 LinkedList 节点中。它完全违反规则,因为键在地图中是唯一的。

参考:堆栈溢出