关于java:equals-hashCode-今天就把你们都认识清楚

43次阅读

共计 5501 个字符,预计需要花费 14 分钟才能阅读完成。

目录

  • Java 中的数据类型
  • 什么时候用关系操作符==, 什么时候用 equals 办法?
  • equals办法, 为什么报了 空指针 java.lang.NullPointerException?
  • hashCode 办法有什么作用?hashCode 和 equals 的关系?
  • 为什么每个笼罩了 equals 办法的类中,也必须笼罩 hashCode 办法?

数据类型

java 中的数据类型,可分为两类:

1. 根本数据类型(原始数据类型)

byte,short,char,int,long,float,double,boolean 他们之间的比拟,利用双等号(==),根底数据类型比拟的是他们的值

2. 援用类型(类、接口、数组)

当他们用(==)进行比拟的时候,比拟的是他们在内存中的寄存地址,
对象是放在堆中的,栈中寄存的是对象的援用(地址)。由此可见 ’==’ 在 比拟的对象是援用类型时, 是对栈中的地址值进行比拟的


关系操作符 ==

java 中蕴含的关系操作符有 小于(<)大于(>)小于或等于 (<=) 大于或等于 (>=) 等于(==)以及 不等于(!=)

==!= 实用所有对象,然而这两个操作符通常在比拟对象的时候会出问题:

在这里 == 和!= 比拟的是 对象的援用。只管对象的内容雷同,然而对象的援用却是不同的,说以 n1==n2 是 false。

        Integer n1 = new Integer(47);
        Integer n2 = new Integer(47);
        
        System.out.println(n1 == n2);  //false
        System.out.println(n1 != n2);  //true

在这里 == 比拟的是 根本数据类型, 那么他会比拟数值是否相等. 所以此时 n1 == n2 输入 true.

        int n1 = 100;
        int n2 = 100;

        System.out.println(n1 == n2);  //true
        System.out.println(n1 != n2);  //false

equals 办法

默认状况, 对象的 equals 办法 调用的是 Object 类中 equals 办法. 源码如下:

    public boolean equals(Object obj) {return (this == obj);  
    }

留神这里相当于还是用的 ==, 在这里比拟的是援用对象, 所以是比拟地址(是不是同一个对象)


第二种状况, 重写了对象的 equals 办法. 例如 String 对象. 源码如下:

public boolean equals(Object anObject) {if (this == anObject) {return true;// 如果是同一个对象间接返回}
        if (anObject instanceof String) {// 是 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) {// 一一字符比拟,若有不相等字符,返回 false
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

此时,equals 办法的重写实现不尽相同, 然而重写后个别都是通过对象的内容是否相等 来判断 对象是否相等, 对于大多数 Java 类库都实现了 equals()办法,以便用来比拟对象的内容,而非比拟对象的援用


防止 equals 办法, 报空指针

防止 equals 办法报空指针, 先通知大家, 答案是应用Objects.equals(a,b), 在 JDK7 增加了一个 Objects 工具类,它提供了一些办法来操作对象,它由一些动态的实用办法组成,这些办法是 null-save(空指针平安的)或 null-tolerant(容忍空指针的),用于计算对象的 hashcode、返回对象的字符串示意模式、比拟两个对象。

在默认状况下, 对象的 equals 办法没有重写 调用的是 Object 类中 equals 办法

那么咱们来写个报错的例子:

        A a = null;// 假如我接管到 config 对象, 我并不知道是否为空, 就进行比拟
        boolean r = a.equals(new B());
        System.out.println(r); // 输入 java.lang.NullPointerException

此时因为咱们的忽略, 接管到参数后, 并没有对参数进行校验, 导致调用 equals 办法报出空指针.

// 其它的例子有:
null.equals("java 宝典");  //NullPointerException

"java 宝典".equals(null);  //false 只有 equals 右边的对象不为 Null 时, 才有后果

null.equals(null);  //NullPointerException

应用Objects.equals(a,b), 左右 两边都为 Null 也不会报空指针

 Objects.equals(null,"java 宝典");  //false
 
 Objects.equals("java 宝典",null);  //false
 
 Objects.equals(null,null);  //true

看一下 Objects.equals 办法的源码, 它是容忍空指针的

    public static boolean equals(Object a, Object b) {return (a == b) || (a != null && a.equals(b));
    }

hashCode() 办法

哈希(Hash)实际上是 人名,因为他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是指将数据依特定算法间接指定到一个地址上, 艰深了解就是一种从任何一种数据中创立小的数字“指纹”的办法。

在 java 中, 默认状况下, 对象没有重写 hashCode()办法. 应用的是 Object 类中的.

  public native int hashCode(); // 它是一个 native 办法.

Object 类定义的 hashCode 办法会针对不同的对象返回不同的整数。(这是通过将该对象的外部地址转换成一个整数来实现的)

例子:

        Config config1 = new Config();
        Config config2 = new Config();

        System.out.println(config1.hashCode());  //1128032093

        System.out.println(config2.hashCode());  //1066516207

        System.out.println(config1.equals(config2));  //false

hashCode 和 equals 的关系

二者均是 Object 类里的办法, 因为 Object 类是所有类的基类,所以所有类里都能够重写这两个办法。

  • 准则 1:如果 x.equals(y) 返回“true”,那么 x 和 y 的 hashCode() 必须相等;
  • 准则 2:如果 x.equals(y) 返回“false”,那么 x 和 y 的 hashCode() 有可能相等,也有可能不等;
  • 准则 3:如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 肯定返回“false”;
  • 准则 4:一般来讲,equals 这个办法是给用户调用的,而 hashcode 办法个别用户不会去调用;
  • 准则 5:当一个对象类型作为汇合对象的元素时,那么这个对象应该领有本人的 equals()和 hashCode()设计,而且要恪守后面所说的几个准则。

在 Java 应用程序执行期间,在对同一对象屡次调用 hashCode 办法时,必须统一地返回雷同的整数,前提是将对象进行 equals 比拟时所用的信息没有被批改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

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

如果依据 equals(java.lang.Object) 办法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 办法 不要求 肯定生成不同的整数后果。然而,程序员应该意识到,为不相等的对象生成不同整数后果能够进步哈希表的性能。


为什么每个笼罩了 equals 办法的类中,也必须笼罩 hashCode 办法?

在每个笼罩了 equals 办法的类中,也必须笼罩 hashCode 办法。如果不这样做的话,就会违反 Object.hashCode 的通用约定,从而导致该类无奈联合所有基于散列的汇合一起失常运作

下面咱们介绍了 hashCode 是什么, 进一步理解 hashCode 的利用,咱们必须先要理解 Java 中的容器,因为 HashCode 只是在须要用到哈希算法的数据结构中才有用,比方 HashSet, HashMap ..

咱们以 hashMap 为例:

HashMap 是由数组和链表组成的存储数据的构造。
确定一个数据存储在数组中的哪个地位
就是通过 hashCode 办法进行计算出存储在哪个地位,
产生抵触的话就会调用 equals 办法进行比对,
如果不同,那么就将其退出链表尾部,如果雷同就替换原数据。
计算地位当然不是下面简略的一个 hashCode 办法就计算出来,两头还有一些其余的步骤,这里能够简略的认为是 hashCode 确定了地位, 代码如下:

public V put(K key, V value) {
        // 如果哈希表没有初始化就进行初始化
        if (table == EMPTY_TABLE) {
            // 初始化哈希表
            inflateTable(threshold);
        }
     
        // 当 key 为 null 时,调用 putForNullKey 办法,保留 null 于 table 的第一个地位中,这是 HashMap 容许为 null 的起因
        if (key == null) {return putForNullKey(value);
        }
     
        // 计算 key 的 hash 值
        int hash = hash(key);
        // 依据 key 的 hash 值和数组的长度定位到 entry 数组的指定槽位
        int i = indexFor(hash, table.length);
        // 获取寄存地位上的 entry,如果该 entry 不为空,则遍历该 entry 所在的链表
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 通过 key 的 hashCode 和 equals 办法判断,key 是否存在,如果存在则用新的 value 取代旧的 value,并返回旧的 value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
     
        // 批改次数减少 1
        modCount++;
        // 如果找不到链表 或者 遍历完链表后,发现 key 不存在,则创立一个新的 Entry,并增加到 HashMap 中
        addEntry(hash, key, value, i);
        return null;
    } 

在下面的办法中, 调用了一个办法能够看到, 数组的下标是依据传入的元素 hashCode 办法的返回值再和特定的值异或决定的:

static int indexFor(int h, int length) {
        // 对 hash 值和 length- 1 进行与运算来计算索引
        return h & (length - 1);
    }

再回到咱们的问题:为什么每个笼罩了 equals 办法的类中,也必须笼罩 hashCode 办法?

如果你重写了 equals,而 hashCode 的实现不重写, 那么类的 hashcode 办法就是 Object 默认的 hashcode 办法,因为默认的 hashcode 办法是 依据对象的内存地址经哈希算法得来的一个值,那么很可能某两个对象明明是“相等”,而 hashCode 却不一样。

这样,当你用其中的一个作为键保留到 hashMap、hasoTable 或 hashSet 中,再以“相等的”找另一个作为键值去查找他们的时候,则基本找不到。导致 HashSet、HashMap 不能失常的运作.

比方:有个 A 类重写了 equals 办法,然而没有重写 hashCode 办法,对象 a1 和对象 a2 应用 equals 办法相等,依照下面的 hashcode 的用法,那么他们两个的 hashcode 必定相等,然而这里因为没重写 hashcode 办法,他们两个 hashcode 并不一样,所以,咱们在重写了 equals 办法后,尽量也重写了 hashcode 办法,通过肯定的算法,使他们在 equals 相等时,也会有雷同的 hashcode 值。


总结

  • == 在比拟 根本数据类型 时, 比拟的是
  • == 在比拟 援用数据类型 时, 比拟的是 对象的援用地址
  • 对象的 equals 办法, 在不重写的状况下, 应用的是 ==, 比拟的是 对象的援用地址
  • 对象的 equals 办法, 在重写当前, 用于比拟对象的内容是否相等, 实现能够应用 IDE 生成或者自定义实现.(例如,String 类对 equals 办法的重写就是一一比拟字符)
  • 不重写的状况下, 对象的 equals 办法 调用的是 Object 类中 equals 办法, 在条件右边为 Null 时会报空指针, 应用 Objects.equals(a,b) 能够防止空指针
  • hashcode 是零碎用来疾速检索对象而应用的
  • 重写了 equals 办法后,也要重写了 hashcode 办法, 否则会导致 HashSet、HashMap 等依赖 hashCode 的容器不能失常的运作

关注公众号:java 宝典

正文完
 0