hashCode和equals方法的重写

我们都知道,在JAVA世界中,万物皆对象。而equals和hashCode这两个方法也在Object类里被定义,先来看这两个方法在Object里面是如何实现的:

先看equals方法

public boolean equals(Object obj) {
        return (this == obj);
    }
equals方法实现很简单,就是将某对象拿来和原始对象进行对比,如果它俩指向同一个对象,那么返回true,否则返回false;


再来看hashCode方法:
public native int hashCode();

这个更简单,竟然只有一个方法定义,连方法实现都没有。注意其中的关键字native, 从网上查了一下才知道,一个Native Method方法就是一个JAVA调用非JAVA代码的接口,native这个方法实现在C++中,也就是说,这个方法的实现不是由JAVA来实现的,仅仅是告知JAVA去调用C++里的一个函数。源代码都在C++里边,我们不需要去理会!

看完这两个我们再来看String 是怎样重写这两个方法的:

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

首先,先来验证anObject 所代表的对象是当前对象吗? 是同一个返回true 结束判断; 否则返回false,然后进入下一步判断,anObject 是否是当前类的一个实例,即对比对象所属同一个类才有比较的意义,猫和猫比较,狗和狗比较,反之,老鼠和向日葵有啥好比较的呢,都不是一个物种对吧。然后两者如果所属同一个类,char[]数组用于存储String 的每一个字符,先进行字符长度对比,再逐个去比较每一个字符,当每一个字符都相同 才代表这两个对象是同一个!!

为啥要重写equals呢???

String  str1 = new String("apple");
String  str2 = new String("apple");
System.out.println(str1==str2);             // false
System.out.println(str1.equals(str2));      // true

用 == 返回 false , == 是用于判断两个对象是否是同一个,而原本Object类里的 equals 方法也是比较两者是否是同一个对象,那如何我们不关心这两个是否是同一个对象,只想知道他们的 值 是不是一样该怎么办? String 类重写了equals 方法就是干这个事的, 比较的是对象的"值"。

接着看String 类的hashCode 方法:

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 字符串先转换成 字符数组,遍历每一个字符,最终返回一个 int 类型的哈希值。

hashCode的存在 ???

  先来了解一下为何会有 hashCode 的存在,  这《JAVA编程思想》一书中 P495页有这么一段话:

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

这段定义在讲啥呢,咋这么乱呢?? 我们可以简单理解成: 为每一个对象都分配一个编号,编号是根据对象的关键特性而决定的,假如对象是人,那么编号就可以根据人的姓名和年龄两个因素一起来决定,毕竟找到两个年龄姓名都相同的不同人还是挺少见的吧。  

那么经过equals比较为true的两个对象肯定有着同样的hashCode(相同的编号);

反之,hashCode相同是否能确定这两个对象equals返回为true呢?? 测试一下:

String s1 = "ac";
String s2 = "bD";
System.out.println("s1的hashCode是: " + s1.hashCode());
System.out.println("s2的hashCode是:" + s2.hashCode());

 输出结果:两个都为 3106;

从这里可以确定的是,hashCode相同,但显然这两个String 不是同一个 ,equals 比较也是 false 的。

hashCode 的优点:

以上说了hashCode初始创建的意义,即给每个对象都编号一个小编号。那我们为啥要编号!!!不编号行不行!!

其实呢,编号的目的是为了简化一些操作,感慨下编写语言的大师们想的都太周到了。学过JAVA的同学都知道HashMap这么一个容器,适用于键值对存储(key-value)。

假设有一个HashMap容器: key:存储丈夫; value:妻子。我们根据key 值来找到这个男人的妻子,key不能存放相同的值。

当我们向这个容器填充 ("李四",“李四的妻子”)时,发现这个容器已经有10000条数据了,我们如何判断这个容器里有没有"李四"这个key呢?? 一条条记录慢慢查询吗?? 李四这个对象里万一有几十个字段,查询太慢了,hashCode就是为了解决这个问题的,为李四生成一个hashCode 编号,存储之前直接查当前容器是否有这么一个hashCode 编号, 没有就把这条记录插进去,有就直接覆盖之前的数据,使得速度大提高。

同样,想查HashMap中是否存在某一条记录,不需要挨个遍历每条数据,那样太慢了!!! 直接找这个key的 hashCode是否存在于HashMap中, 不存在直接返回false, 存在则继续进行equals的比较。

把Effective书中的话搬过来了,对比前面的测试应该好理解了:

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

最后总结:

1) 只要重写 equals,就必须重写 hashCode。

2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的 对象必须重写这两个方法。

3) 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals。

猜你喜欢

转载自blog.csdn.net/qq_33378853/article/details/83046826
今日推荐