“==”、“equals()”、“hashcode()”之间的秘密

11次阅读

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

前言
万丈高楼平地起,今天的聊点基础而又经常让人忽视的话题,比如“==”与“equals()”区别?为何当我们重写完 ”equals()” 后也要有必要去重写 ”hashcode()” 呢?… 带着这些问题,我们一起来探究一下。
概念
“==”: 它主要是判断符号两边的“对象”的值是否相等,而这里的“值“”又有所区分了。
基础数据类型:比较的就是自身的值,这个跟我们常规的理解是基本一致的。
引用数据类型:比较的对象的内存地址。
“equals()”: 它也是用来判断两个对象是否相等,所以也得分不同的情况来说明。
在当前类中,没有重写 equals 方法的话,默认的实现跟 ”==” 的实现是一样的。下面是 Object 类的 equals 方法实现。
在当前类中,重写了 equals 方法,此时判断的依据就是你重写的逻辑。
怎样重写 equals() 方法?

1、自反性:对于任何非空引用 x,x.equals(x) 应该返回 true。
2、对称性:对于任何引用 x 和 y,如果 x.equals(y) 返回 true,那么 y.equals(x) 也应该返回 true。
3、传递性:对于任何引用 x、y 和 z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,那么 x.equals(z) 也应该返回 true。
4、一致性:如果 x 和 y 引用的对象没有发生变化,那么反复调用 x.equals(y) 应该返回同样的结果。
5、非空性:对于任意非空引用 x,x.equals(null) 应该返回 false。

由此可以看出,重写一个 equals() 方法,需要注意的点还是比较多的,这里给出一个参考的事例。
public class EqualsDemo {
private String name;
private String info;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

EqualsDemo that = (EqualsDemo) o;

if (name != null ? !name.equals(that.name) : that.name != null) return false;
return info != null ? info.equals(that.info) : that.info == null;
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (info != null ? info.hashCode() : 0);
return result;
}
}
有些读者可能会感到奇怪,不是说重写 equals() 方法吗,为什么这里又出现了一个 hashcode()? 所以这里又引出了我们的另一个主角 hashcode() 方法,当我们重写了 equals() 方法后,它就一定会出现,也会“吵着“自己也要被重写。
什么是 hashcode()?
hashCode() 的作用是获取哈希码,也称为散列码;它返回的一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode 方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括 HashSet、HashMap、HashTable 等。它定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
当我们在上面的集合插入对象的时候,java 是怎么知道里面是否有重复的对象呢?可能大家第一反应是 equals 方法,没错这方法可以实现这个功能,但是当集合里面有成千上万个元素的时候,效率会如何呢?答案当然是比较差了,所以才会出现了哈希码。
public V put(K key, V value) {
// 判断当前数组是否等于 {},若是则初始化数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 判断 key 是否等于 null,是则将把当前键值对添加进 table[0] 中,遍历 table[0] 链表
// 如果已经有 null 为 key 的 Entry,则修改值,返回旧值,若无则直接添加。
if (key == null)
return putForNullKey(value);
//key 不为 null 则计算 hash
int hash = hash(key);
// 搜索对应 hash 所在的 table 中的索引
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 修改次数
modCount++;
addEntry(hash, key, value, i);
return null;
}
这里是 jdk7 中 Hashmap put() 方法的实现,通过源码的注释可以看出执行的流程,需要更详细的了解 HashMap 可以参考我之前发在开源中国的博客《Java7 HashMap 全面解读!》,链接:https://my.oschina.net/199212…
经过概念的介绍,知道为什么重写完 equals() 后要接着重写 hashcode() 了吧?
People p1=new People(“ 小明 ”,18);
People p2=new People(“ 小明 ”,18);
此时重写了 equals 方法,p1.equals(p2) 一定返回 true,假如只重写 equals 而不重写 hashcode,那么 Student 类的 hashcode 方法就是 Object 默认的 hashcode 方法,由于默认的 hashcode 方法是根据对象的内存地址经哈希算法得来的,显然此时 s1!=s2, 故两者的 hashcode 不一定相等。所以在一些集合的使用当中会出现问题。
总结
小小的几个方法,没想到却有这么多“坑”,而且在面试中也会经常被问到,在金三银四的时候,但愿各位不会陷在这里。

喜欢的话,关注一下微信公众号《深夜里的程序猿》,每天更新高质量 IT 文章

正文完
 0