equals和hashcode的理解

为什么Object需要hashCode()这个方法?用意何在?

学过数据结构的都知道,哈希表或散列表是一种查询速度比较快的数据结构。时间复杂度O(1)。在Java语言中,类Object中有一方法hashCode()

public native int hashCode();

但是,很少有人思考为什么?如果说两个数进行比较需要equals()方法这个是合情合理的,但是为啥需要hashCode()呢?假如说当数据量很大时,比如10万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题,尤其是对性能要求严格的系统。这个时候hashCode()能够减少equals()的比较次数,大大提升效率。我觉得Object类设计hashCode()来提升比较的效率。

为什么重写equals()方法必须重写hashCode()方法?

有时候在想,是不是可以直接根据hashCode()值判断两个对象是否相等吗?答案是否定的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。也就是说对于两个对象,

  • 如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
  • 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
  • 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
  • 如果两个对象的hashcode值相等,则equals方法得到的结果未知。

为什么hashCode()方法实现用的31这个质数?

        /**
     * 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]

那么,为什么是31呢?大概有如下原因:

  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)

  • 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)

  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突,具体哈希冲突的解决策略大家看看网上查查)

  • 并且31只占用5 bits,相乘造成数据溢出的概率较小。

      /**
       * @param radix  进制 eg: 2进制
       * @param a  二进制字符串数组 eg: {1,1,0,0}
       * @return
       */
      private static int calHashCode(int radix,int[] a){  
          int sum = 0;  
          for(int i=0;i<a.length;++i){  
              sum = sum*radix + a[i];  
          }  
          return sum;  
      }  

为什么hashCode()依赖于对象中易变的数据时需要当心?

对于依赖对象的易变数据,当然需要当心,不然你会出错的。在Effactive java中有如下一段话:

  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数

    在Java编程思想中同样有一句话:

“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果将一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

​ 在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段

下面给出一个示例:

public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        //result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
//      if (age != other.age)
//          return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    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;
    }

}

import java.util.HashMap;

public class Main {

     public static void main(String[] args) {
           Person p = new Person("DengYu", 30);
           System.out.println(p1.hashCode());   
           HashMap<Person, Integer> hashMap = new HashMap<Person, Integer>();
           hashMap.put(p, 1);
           p.setAge(31);
           //在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。
           System.out.println(hashMap.get(p));
           System.out.println(hashMap.get(new Person("DengYu",30)));
       }
}

参考文章

1浅谈Java中的hashcode方法 海子 (https://www.cnblogs.com/dolphin0520/p/3681042.html) http://www.cnblogs.com/dolphin0520/p/3681042.html

2 如何重写hashCode()和equals()方法 王鸿飞 https://blog.csdn.net/neosmith/article/details/17068365

3为什么在定义hashcode时要使用31这个数呢? steveguoshao https://blog.csdn.net/steveguoshao/article/details/12576849

4 为什么在定义hashcode时要使用31这个数呢?小M的专栏 https://blog.csdn.net/mingli198611/article/details/10062791

5 Java之音 http://www.javazhiyin.com/?p=513

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
equalshashCode和==是Java中用于比较对象的方法,它们在某种程度上是相关的但又有不同的作用。 ==运算符是用于比较两个对象是否完全相同,即比较对象的引用地址。如果两个对象的引用地址相同,则==运算符返回true;如果两个对象的引用地址不同,则==运算符返回false。需要注意的是,==不能用于比较对象的内容是否相等。 equals方法是用于比较两个对象的内容是否相等。默认情况下,equals方法和==运算符具有相同的行为,即比较对象的引用地址。但可以重写equals方法以实现自定义的对象相等性比较。在重写equals方法时,需要满足以下条件: 1. 对称性:若a.equals(b)返回true,则b.equals(a)也必须返回true。 2. 自反性:对于任何非null的引用值x,x.equals(x)都必须返回true。 3. 传递性:若a.equals(b)返回true,并且b.equals(c)返回true,则a.equals(c)也必须返回true。 4. 一致性:在多次调用equals方法时,对象的内容没有改变,则多次调用equals方法的结果应该保持一致。 hashCode方法返回对象的哈希码,可以理解为对象的唯一标识。hashCode方法的主要作用是在使用哈希表等基于哈希算法的数据结构时提高查找效率。需要注意的是,若两个对象的内容相等,它们的hashCode值应该相等。因此,当重写equals方法时,通常也需要重写hashCode方法,以保证对象相等时它们的hashCode值也相等。 需要实现类的这三个方法的情况一般有以下几种: 1. 想要比较两个对象的内容是否相等,需要重写equals方法,常常需要同时重写hashCode方法。 2. 自定义的类要作为Map的键或Set的元素,需要重写equalshashCode方法,以保证对象的相等性判断正确。 3. 自定义的类需要实现深度拷贝或需要序列化/反序列化操作,需要重写clone方法,通常也需要重写equalshashCode方法。 综上所述,equalshashCode和==三者的关系是:==比较对象的引用地址,equals比较对象的内容是否相等,hashCode返回对象的哈希码,它们各自有不同的使用场景和目的。在某些需要用到对象比较的特定场景下,需要重写equalshashCode方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值