为什么重写equals方法时必须重写hashCode方法

Java中的hash值主要是用来在散列存储结构中确定对象的存储地址的,提高对象的查询效率。

Java设计的顶级父类Object类中,有两个方法很特殊,它们分别是equals方法与hashCode方法。——一旦重写了equals方法,就一定要重写hashCode方法。以下是Object的源码:

public class Object {

 /* 
  * Note that it is generally necessary to override the {@code hashCode}
  * method whenever this method is overridden, so as to maintain the
  * general contract for the {@code hashCode} method, which states
  * that equal objects must have equal hash codes.
  */
  public boolean equals(Object obj) {
    return (this == obj);
  }

 /*
  * The general contract of {@code hashCode} is:
  * <ul>
  * <li>Whenever it is invoked on the same object more than once during
  *     an execution of a Java application, the {@code hashCode} method
  *     must consistently return the same integer, provided no information
  *     used in {@code equals} comparisons on the object is modified.
  *     This integer need not remain consistent from one execution of an
  *     application to another execution of the same application.
  * <li>If two objects are equal according to the {@code equals(Object)}
  *     method, then calling the {@code hashCode} method on each of
  *     the two objects must produce the same integer result.
  * <li>It is <em>not</em> required that if two objects are unequal
  *     according to the {@link java.lang.Object#equals(java.lang.Object)}
  *     method, then calling the {@code hashCode} method on each of the
  *     two objects must produce distinct integer results.  However, the
  *     programmer should be aware that producing distinct integer results
  *     for unequal objects may improve the performance of hash tables.
  * </ul>
  * <p>
  * As much as is reasonably practical, the hashCode method defined by
  * class {@code Object} does return distinct integers for distinct
  * objects. (This is typically implemented by converting the internal
  * address of the object into an integer, but this implementation
  * technique is not required by the
  * Java&trade; programming language.)
  *
  */
  public int hashCode() {
    return identityHashCode(this);
  }


  /* package-private */ static int identityHashCode(Object obj) {
    int lockWord = obj.shadow$_monitor_;
    final int lockWordStateMask = 0xC0000000;  // Top 2 bits.
    final int lockWordStateHash = 0x80000000;  // Top 2 bits are value 2 (kStateHash).
    final int lockWordHashMask = 0x0FFFFFFF;  // Low 28 bits.
    if ((lockWord & lockWordStateMask) == lockWordStateHash) {
      return lockWord & lockWordHashMask;
    }
    return identityHashCodeNative(obj);
  }


}

如果一个类没有重写equals(Object obj)方法,则等价于通过==比较两个对象,即比较的是对象在内存中的空间地址是否相等;如果重写了equals(Object obj)方法,则根据重写的方法内容去比较相等,返回true则相等,false则不相等。

equals方法注释中的大致意思是:当我们将equals方法重写后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。

hashCode方法本质就是一个哈希函数,Object类的作者在注释的最后一段的括号中写道:将对象的地址值映为integer类型的哈希值。

提到hashCode,就会想到哈希表,将某一对象的值映射到表中的某个位置,从而达到以O(1)的时间复杂度来查询该对象的值。hashCode是一个native方法,哈希值的计算利用的内存地址。哈希表在一定程度上也可以起到判重的作用,但也可能存储哈希冲突,即使是两个不同的对象,它们的哈希值也可能是相同的。因此,虽然哈希表具有优越的查询性能,但也可能存在哈希冲突。

hashCode方法注释中列了个列表,列表中有三条注释,当前需要理解的大致意思如下:

一个对象多次调用它的hashCode方法,应当返回相同的integer(哈希值);
两个对象如果通过equals方法判定为相等,那么就应当返回相同integer。
两个地址值不相等的对象调用hashCode方法不要求返回不相等的integer,但是要求拥有两个不相等integer的对象必须是不同对象。

图中存在两种独立的情况:

相同的对象必然导致相同的哈希值;
不同的哈希值必然是由不同对象导致的;

因此,equals方法与hashCode方法根本就是配套使用的。对于任何一个对象,不论是使用继承自Object的equals方法还是重写equals方法。hashCode方法实际上必须要完成的一件事情就是,为该equals方法认定为相同的对象返回相同的哈希值。如果只重写equals方法没有重写hashCode方法,就会导致``equals`认定相同的对象却拥有不同的哈希值。

Object类中的equals方法区分两个对象的做法是比较地址值,即使用==。如果根据业务需求改写了equals方法的实现,那么也应当同时改写hashCode方法的实现。否则hashCode方法依然返回的是依据Object类中的依据地址值得到的integer哈希值。

代入到具体的例子 –String类,在String类中,equals方法经过重写,具体实现源码如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

  public boolean equals(Object anObject) {
    if (this == anObject) {
      return true;
    }
    if (anObject instanceof String) {
      String anotherString = (String) anObject;
      int n = length();
      if (n == anotherString.length()) {
        int i = 0;
        while (n-- != 0) {
          if (charAt(i) != anotherString.charAt(i))
            return false;
          i++;
        }
        return true;
      }
    }
    return false;
  }

}



通过源码可以看到,String对象在调用equals方法比较另一个对象时,除了认定相同地址值的两个对象相等以外,还认定对应着的每个字符都相等的两个String对象也相等,即使这两个String对象的地址值不同(即属于两个对象)。

String类中对equals方法进行重写扩充了,但是如果此时我们不将hashCode方法也进行重写,那么String类调用的就是来自顶级父类Obejct类中的hashCode方法。即,对于两个字符串对象,使用它们各自的地址值映射为哈希值。 也就是会出现如下情形:

也就是说,被String类中的equals方法认定为相等的两个对象拥有两个不同的哈希值——因为它们的地址值不同。

为什么重写equals方法就得重写hashCode方法?—— 因为必须保证重写后的equals方法认定相同的两个对象拥有相同的哈希值。同时我们也得出了——hashCode方法的重写原则就是保证equals方法认定为相同的两个对象拥有相同的哈希值。

equals里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么既然hashCode效率这么高为什么还要使用equals进行比较呢?

因为hashCode并不是完全可靠,有时候不同的对象生成的hashcode也会一样(hash冲突),所以hashCode只能说是大部分时候可靠,并不是绝对可靠。 所以可以得出:

equals相等的两个对象,它们的hashCode肯定相等,也就是用equals对比是绝对可靠的;
hashCode相等的两个对象,它们的equals不一定相等,也就是hashCode不是绝对可靠的;
所有对于需要大量并且快速的对比的话如果都用equals去做显然效率太低,解决方式是,每当需要对比的时候, hashCode去对比,这就用到了哈希表,能够快速的地位到对象的存储位置,如果hashCode不一样,则表示这两个对象肯定不相等(也就是不必再用equals去再对比了),如果hashCode相同,此时再对比它们的 equals,如果equals也相同,则表示这两个对象是真的相同了。

查看字符串的hashCode:

String str = new String("abc");
System.out.println(str.hashCode()); // 96354
str += "a";
System.out.println(str.hashCode()); // 2987071
str += "b";
System.out.println(str.hashCode()); // 92599299
String str1 = "abcab";
System.out.println(str.hashCode() + " " + str1.hashCode()); // 92599299 92599299

String str2 = "ab";
String str3 = new String("ab");
System.out.println(str2.hashCode() + " " + str3.hashCode()); // 3105 3105

以下是String.hashCode的源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
  
  private int hash; // Default to 0
  
   /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

}


由上面的代码可知,String类的hashCode的值为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],其中s是字符串对应的char数组,所以字符串内容一样的String对象,调用hasCode()返回值是一样的。但是反过来,当hashCode返回的值一样时,其字符串内容不一定一样,因为上面的方法计算hash时可能会发生位数溢出。

为了使哈希表容器(HashMap、HashSet等)能正常工作:

【Java】为什么equals()和hashcode()要同时重写?_麦格马戈登的博客-CSDN博客_为什么要同时重写hashcode和equals

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 重写equals方法必须重写hashcode方法,是因为在Java中,如果两个对象的equals方法返回true,则它们的hashcode方法必须返回相同的值。如果不重写hashcode方法,那么两个相等的对象可能会有不同的hashcode值,这会导致它们无法正确地被放入基于哈希表的集合中,例如HashSet、HashMap等。因此,为了保证对象在哈希表中的正确性,必须重写equalshashcode方法。 ### 回答2: 在 Java 语言中,equalshashCode 是 Object 类的两个方法,它们都用来比较对象的相等性。equals 实际上是比较两个对象是否相等,而 hashCode 方法返回的是一个散列码(即哈希码),它是用来确定对象在哈希表中的位置的。 哈希表是一种数据结构,它可以用来实现集合、映射等数据结构。在 Java 中,哈希表被广泛应用于各种数据结构中,如 HashMap、HashSet 等。当对象被添加到哈希表中,哈希表会根据对象的哈希码确定对象在哈希表中的位置,从而实现高效的访问。因此,如果重写equals 方法,但没有重写 hashCode 方法,则会导致对象无法正确地插入到哈希表中,从而影响哈希表的性能和正确性。 为了避免这种问题,我们需要确保重写equals 方法的同重写hashCode 方法。当两个对象相等,它们的 hashCode 值也必须相等;反之,当两个对象的 hashCode 值相等,并不能说明它们相等,因为存在哈希冲突的可能。因此,在重写 hashCode 方法,通常需要考虑对象的所有用于比较相等性的字段,以保证哈希码的唯一性和一致性。 在实际编程中,我们经常会使用 IDE 自动生成 equalshashCode 方法。然而,如果对象的字段发生变化,我们必须重新生成 hashCode 方法,否则仍会出现哈希冲突和性能问题。因此,我们建议及重写 hashCode 方法,以保证代码的正确性和可维护性。 ### 回答3: 在Java中,equals方法用于比较两个对象是否相等。但是,equals方法并不能直接判断两个对象是否相等,它需要使用hashcode方法进行辅助判断。如果两个对象的hashcode相同,那么它们并不一定相等,需要使用equals方法再次判断。 因此,当我们重写equals方法,需要同重写hashcode方法。如果我们只重写equals方法而不重写hashcode方法,那么可能会导致出现以下两个问题。 第一个问题是不符合规范。在Java中,如果两个对象的equals方法返回值相等,那么它们的hashcode方法的返回值也必须相等。如果我们只重写equals方法而不重写hashcode方法,就可能会导致这种情况的出现,违反了Java规范。 第二个问题是影响性能。在Java中,如果两个对象的hashcode方法的返回值不相等,那么它们一定不相等。如果我们只重写equals方法而不重写hashcode方法,那么每次调用equals方法都会进行完整的比较,这会对性能产生很大的影响。而如果我们同重写hashcode方法,那么可以将对象按照hashcode的返回值进行分类,然后只需要比较同一个hashcode分类中的对象是否相等,这样可以大大提高比较的效率。 因此,为了保证代码的规范和性能,我们在重写equals方法必须重写hashcode方法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值