深入详细的理解hashCode()和equals()之间的关系

首先,我们要知道标题所示两个方法均是基于散列表和散列算法才有其联系意义的!

而,java中散列表使用链表数组实现。

先来简单叙述散列表:是一种可以快速查找特定对象的数据结构。散列表可以对每一个对象计算一个整数,成为散列码。而根据这个散列码,通过一定的散列算法,可以迅速计算出对象的位置!

我们通过HashSet来简单理解,散列表的实现机制!

当向hashSet中添加元素a的时候,首先调用其a.hashCode()方法获得其散列码,从而计算出其在数组中的位置,也就是其所在的链表位置。定位到某个具体链表后,接下来调用a.equals()方法用元素a的值和当前定位链表中的每一个元素进行比较,如果有相同元素,a将被视为已经添加过,从而不再重复的添加,否则将a添加到当前链表表头!

 需要注意的是,这里所谓的相同的逻辑是,基于equals方法的比较逻辑!

好了,以上内容均为引入具体内容所需要的知识!

进入正题:

问题引入:两个对象的hashCode()相同,则equals一定返回为true,对吗?

答案是:否定的! 

因为:类的hashcode方法和equals方法,是可以重写的,故其放回值完全取决于其方法的处理逻辑!

那么,该问题有了扩展问题:

1.既然方法是可以重写的,那其默认不重写的时候,是如何的呢?

答案是:这两个方法均为基类Object中的方法,在Object中均有其默认实现:

Object中的hashcode()方法,其值对应于类的内存地址!

Object中的equals()方法,其比较结果与“==”号一致,也就是判断的是引用是否相等!

下面是这两个方法的默认实现下的联系:

此处附上jdk官方所给的详细说明:

下面的话来自JDK:

        hashCode         

        public int hashCode()返回该对象的哈希码值。

        支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。        

hashCode 的常规协定是: 
        在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。

        从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。        

        如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。        

        如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。        

        实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 

2.此时,我们就需要来讨论另一个问题了,那就是两个方法进行重写的时候:

根据上述常规性协定的描述,我们了解到了两个方法之间的内在联系!

那么,重写的时候,我们也要遵循上述所示的常规协定:

即:两个对象用equals比较返回为true,那么对象的hashcode方法必须返回相同的结果!

即:a.equals(b) is true;  必须保证:a.hashCode() 和 b.hashCode()一致

所以,当我们重写euqals方法的时候,一般都必须要重写hashCode()方法来满足这个协定!

而满足这个协定的一般解决思路:

      在equals中进行比较的属性,一定也要用到hashCode方法中去计算散列码!

那么,重写这两个方法的意义是什么?

简单来说:

重写equals,是为了保证相同的元素,无法被同时添加到散列表中!

很容易理解:有个员工类,编号属性作为其唯一区分的依据,那么不重写equals时,同时向散列表添加两个不同的对象,但是其编号是相同的,问题不就出现了吗?就相当于,添加了两个一样的员工,而散列表却不知道!因为此时不重写equals,equals的判断逻辑是==,判断的是引用比较,很显然,两个对象的引用地址是不同的!所以,我们要重写equals,将逻辑变为去比较两个对象的id属性,以达到我们想要的判断逻辑!

重写hashcode,一般都是为了使散列法具有高效的查找速度,以及满足相应上述所说的常规协定!

那么,问题又来了,为什么非得有这么一个协定呢?

其实,很简单,基础来说!hashCode()方法所计算出的散列码就是为了去找到散列表中的某一条特定的链表,也就是数组位置,这个协定是为了保证,equals判断相等的两个对象,不可以被分到两个链表中去,从而失去了equals判断的逻辑,也就直接导致了散列表中添加了两个一样的对象。

理解了上述内容,最后,也提出一般equals自身重写所必需的满足的硬性条件:

①自反性:a.equals(a) is true

②对称性:a.equals(b) 和 b.equals(a) 结果一致

③传递性:a.equals(b) is true,b.equals(c) is true, so a.equals(c) is true

④持续性:多次调用a.equals(b)的返回结果应该一致

⑤对任意非空a:a.equals(null) is false


 

猜你喜欢

转载自blog.csdn.net/romantic_jie/article/details/97421377