2022-08-31  阅读(2)
原文作者:唯一浩哥 原文地址:https://www.jianshu.com/u/c52a29db48b6

概述

equals方法和hashCode方法都是有Object类定义的。

            public class Object {
                public native int hashCode();
                public boolean equals(Object obj) {
                    return (this == obj);
                }
            }

任何的类都是Object类的子类,所以它们默认都拥有这两个方法。
equals方法用于定义两个对象的比较方式,而hashCode方法是native方法,主要用户计算对象的hash值。

equals

equals方法主要用于定义两个对象的比较方式,默认的比较方式是比较内存地址,相对于基本类型来说就是值,而相对于引用类型来说就是堆中具体对象的地址。那么就只有值相同的基本类型,和同一个对象的两个引用才能相等。但是在我们实际业务系统中,两个对象的相等一般指的是两个对象的内容相同(逻辑相同),而不是说它两个是同一个对象,这种情况使用默认的equals就无法实现相等(因为两个不同对象地址值一定不同),这时候我们就需要对equals方法进行重写,定义新的比较方式。

准则

  • 自省性:对于非null的x,存在:x.equals(x)返回true
  • 对称性:对于非null的x和y,存在:x.equals(y)==y.equals(x)
  • 传递性:对于非null的x、y、z,存在:当x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定为true
  • 一致性:对于非null的x和y,多次调用x.equals(y)所得的结果是不变的
  • 非空性:对于非null的x,存在x.equals(null)返回false

重写

其实Java中已经为我们展示了如何重equals方法了,最经典的就是String的equals方法:

            public final class String
                implements java.io.Serializable, Comparable<String>, CharSequence {
                public boolean equals(Object anObject) {
                    // 首先判断两个对象是不是同一个,地址相同否
                    if (this == anObject) {
                        return true;
                    }
                    // 判断给定的对象是否是String类型,这里instanceof关键字是重写equals方法时经常使用的一个关键字
                    // instanseof用于判断右边的类型是否是当前对象的类型或者超类型,超接口类型等
                    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;
                }
            }

注意,使用instanceof在针对存在子类的情况下,可能会出现违反对称性和传递性的情况,为了避免这种情况,可以通给getClass的方式比较类型。
自定义重写:

            public class EqualsTest {
                private int id;
                public int getId() {
                    return id;
                }
                public void setId(int id) {
                    this.id = id;
                }
                @Override
                public boolean equals(Object obj) {
                    // 满足非空性
                    if(obj == null){
                        return false;
                    }
                    // 满足自省性
                    if(this == obj){
                        return true;
                    }
                    // 满足对称性、传递性、一致性
                    if(this.getClass() == obj.getClass()
                            && this.getClass().getClassLoader() == obj.getClass().getClassLoader()
                            && this.id == ((EqualsTest)obj).getId()){
                        return true;
                    }
                    return false;
                }
            }

注意:这里如果是有不同的类加载器加载的同一类的实例也是无法相等的。

hashCode

hashCode一般用于计算对象的hash值,它在类重写equals的时候一起重写,重写它的目的是为了保证equals相同的两个对象的hashCode结果一致,为什么要保证这一点呢,那就归结到java中的那几个基于Hash实现的集合上了,比如HashMap、HashSet等,这些集合需要用到对象的hash值来参与计算定位。
使用hashCode的目的就是为了散列元素,最终元素能否散列均匀和hashCode的实现息息相关,即为hash函数。

实现方式

  • 链地址法(理解):在出现hash冲突的时候,在这个位置再插入新元素,并与原有元素形成一个链表,类似于HashMap的实现方式

  • 开放寻址法(了解):在出现hash冲突的时候,在当前位置的附近寻找空位来存放新元素,这种方式只需要一种数据结构,不需要引入新的数据结构。其实就是为每个hash结果准备一个探查序列,用来存放发生hash冲突的元素。

    • 线性探查法:当出现hash冲突,则在当前位置逐个向后寻找空位,将新元素保存到找到的第一个空位,当找到最后时,需要折返到一开头继续查找。由于探查序列固定,所以会引发一次集群问题。
    • 二次探查法:出现冲突,不再逐个顺序探查,而是由某种函数计算的结果序列来探查,这个函数依赖于开始下标的平方,所以叫二次探查,开始下标的不同,序列就不相同,不同序列中会有重复的下标,由于每个下标开始的探查序列是固定的,所以会引发小规模集群,即二次集群问题。
    • 双重散列法:要解决群集,就要想办法让相同hash结果的序列不同,最好让序列函数依赖于元素本身,保证当元素不同时,即使hash结果一致,但一旦发生冲突,不同的元素的序列是不同的(因为序列还要依赖元素本身,元素不同,序列结果就会不同),这样存在两个依赖变量的探查方法,可以极大的避免集群问题。
  • 再HASH法(知道)

  • 建立公共溢出区法(知道)

hashCode的实现方式并不是随手而来的,需要考虑各种情况,选择合适的方式来实现,举个例子,在Java的HashMap集合中,采用的就是链地址法来处理hash冲突。

参考:


来源:https://www.jianshu.com/u/c52a29db48b6


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文