equals()、hashcode()、==

equals()、hashcode()、==

equals()和==之间的关系

equals() 定义在Object类中,意味着所有的java类中都实现了这个方法。其底层其实就是通过==来进行比较,也就是说通过比较两个对象的内存地址是否相同判断是否是同一个对象。

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

也就是说在未重写equals方法时equals方法等价于==
当我们不想仅仅通过两个对象的地址是否相同来判断两个对象的相等关系时,我们会重写equals方法
比如在String类中重写了equals的方法。

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value, aString.value)
                              : StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}

总结一下
==是判断两个对象的地址是否相等,即判断是否是同一个对象。
equals()则有两种情况,第一种情况就是没有被重写的equals(),它就相当于通过==去比较;第二种情况是重写了的,一般重写后都是去比较两个对象的内容(或者说属性)是否相等,具体看重写的方法。

hashcode()

hashcode()同样也定义在Object类中,意味着java中任何类都有这个函数。Object中的hashcode方法是本地方法,也就是说其是由c/c++去实现的。

public native int hashCode();

hashcode可以起到一定的判重作用,但是可能会存在哈希冲突,所以两个不同的对象其哈希值也有可能是相同的。但是这并不影响哈希表优越的查询性能,在使用hash表时,当我们得出哈希值后,我们是可以直接通过数组的索引来找到该索引下的数据的。

如果一个集合中含有1000个元素,假定这个集合中是不允许有重复元素的。那么现在要插入一个新的元素该怎么办?难道和前面1000个元素一个个去对比判重,这样效率明显会很低。但如果使用哈希表的话,我们只需通过哈希函数计算出哈希值,然后找到散列表对应的位置存入就好了。其时间复杂度为O(1)

其实在一般的类中hashcode并没有什么作用,一般hashcode只有在散列表中才有作用,像一些本质是散列表的类:HashMap、HashSet、HashTable

一定要记住,如果两个对象相等,那么他们的hashcode值一定要相等;但两个hashcode值相等的对象,它们并不一定是相等的。(这里指的是在散列表的情况,非散列表情况下不一定,下面会解释)

equals()和hashCode()的关系

我们在编程中,数据存储是我们必须会接触到的。Java中封装了一系列操作数据的 api,降低了数据操作的复杂度。但在我们对数据进行操作之前,首先要把数据按照一定的数据结构保存到存储单元中,否则操作数据将无从谈起。

然而存储数据的方式有很多种,其各有各的特点。 Java 根据不同的数据结构提供了丰富的容器类,方便程序员选择适合业务的容器类进行开发。Java 的容器类被分为 Collection 和 Map 两大类,Collection 又可以进一步分为 List 和 Set。 其中 Map 和 Set 都是不允许元素重复的,严格来说Map存储的是键值对,它不允许重复的键值。而Map和Set中大多数实现类底层都是散列表结构。像上面所说的HashMap、HashSet、HashTable。

其实通过上面对equals()和hashCode()二者特点的说明,应该会有一些眉目了。当我们要在一个不能有重复元素的集合中存放新元素时,难道直接调用equals()去和已有的元素一个一个比较吗?对于元素少的集合,确实可以,但当你的元素多了后,用equals()很明显效率会低,虽然逻辑简单,但它的时间复杂度依然是O(n)。

但如果有散列表的配合,也就是先通过散列表进行判重。通过新元素计算出的哈希值,找到对应的哈希表位置,判断对应的内存单元是否有产生冲突,也就是判断对应的位置是否已经存有对象,如果没有,就可以直接插入。而如果产生了冲突,也就是说该位置之前已经存有元素了,那么这时就使用equals()进行比较,进一步判断两个对象是否相同。

配合散列表的操作看似复杂了一些,但其带来的效率是大大提升的。要知道,当运用 hashCode() 时,判断是否有相同元素的代价,只是一次哈希计算,时间复杂度为O(1),只有在hashcode()值相同时,才会通过equals进行比较。大大减少了equals比较的次数,极大地提高了数据的存储性能。

我们再回到最开始的那个问题,重写equals()方法必须重写hashcode()方法。通过上面的讲解应该能大致明白为什么了吧。因为当你需要将该对象存入到底层为散列表结构的集合中时,是先判断hashcode值,碰到相同值时再通过equals进一步判断。所以两个方法都需要重写,保证如果两个对象是相等的,它们的 equals() 方法应该要返回 true,它们的 hashCode() 需要返回相同的结果。但这其实是针对当该类会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中用到的时候这种情况。

其实官方文档的原话是(这是在Object类中定义equals()方法上的一段注释)

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
官方文档提醒我们当重写 equals() 方法的时候,通常是有必要重写 hashCode() 方法。其实并不是强制性的,因为在一些情况下,hashCode()和equals()是没有关系的。比如当该类不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中用到的时候,这种时候equals() 用来比较该类的两个对象是否相等,而hashCode() 则根本没有任何作用。

总结:
判断两个元素是否相等时我们一般会直接重写equals方法,然后直接调用equals方法
此时hashcode并未起到任何作用

但是当我们使用HashMap、HashSet、HashTable这些数据结构时。由于他们不允许表中有相同的元素,所以当放入一个新的元素,他们会判断该元素是否和表中已经有的元素相同。
如果直接调用equals方法这样需要顺序和表中的元素进行比较o(n)效率极低
所以会先计算出存入元素的hashcode,判断该hashcode下有无对应的元素。这样的效率就是o(1)了
当出现hashcode相同时,再调用equals方法,判断是否相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值