1.什么是equals方法
源码中的注释:equals方法实现了非空对象的引用之间的等价关系
* The {@code equals} method implements an equivalence relation on non-null object references:
public boolean equals(Object obj) {
return (this == obj);
}
因为 == 比较的是两个对象的地址(基本类型比较数值),所以对于没有重写equals方法的对象来说,equals方法就是比较二者的地址是否相等。但我们代码中实际判断两个对象是否相等,是根据自己的需求来对比的(而不是地址)。这时候就需要重写equals方法实现自己的比较逻辑。
2.hashcode方法
源码中的注释:返回对象的一个hashcode,这个方法是为了支持哈希表(比如HashMap)的好处
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
哈希表:哈希表根据对象的hashcode来计算出对象在哈希表中的存放位置(hashcode的作用)
3.分析
HashTable的put方法源码
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; //基于hashcode计算存放位置
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
在HashTable中判断元素是否相等的过程是这样的
if ((entry.hash == hash) && entry.key.equals(key))
这就是重写equals方法同时要重写hashcode方法的原因:
哈希表判断两个对象是否相同是根据两个对象的hash值与equals结果来判断的,我们前面说过,重写equals方法,实现自己的比较逻辑来判断二者是否相等,但是如果不重写hashcode方法,两个对象的hashcode方法返回的就是不一样的值,这样的话,就会导致哈希表认为相等的二者是不相等的,为了防止这样的错误出现,文档规定:重写一个对象的equals方法的时候,也要重写hashcode方法,当两个对象equals返回true时,他们的hashcode也要相同。
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
4.拓展
- 为什么HashTable中判断两个对象是否相同要同时判断hashcode与equals呢
if ((entry.hash == hash) && entry.key.equals(key))
原因是性能
当两个对象hash值相同的时候,对象可能相同可能不同
当两个对象hash值不同的时候,对象肯定不同
所以HashTabel在判断对象是否相同时,先判断hash值,hash值不同说明两个对象不相同直接返回,如果hash相同,再进行equals的判断。
这里我个人分析一下性能的对比:
(1) == 运算符
虚拟机中判断两者是否相等的其中一个指令是if_icmpne(所有比较指令都大同小异,差别不过是类型的不同和根据判定结果跳转的地方不同) if_icmpne的执行过程是:先将操作数栈顶的两个元素出栈,进行大小比较,如果相同,入栈1代表boolean为true,不相同入栈0代表boolean为false。这就是 == 操作符的执行流程,非常简单。
(2)equals方法
虚拟机中要执行一个方法,就要先“拼装”一个栈帧(栈帧是方法执行的基本单位,方法的开始到结束的过程,对应着栈帧在虚拟机栈中的入栈和出栈的过程),进行把相关参数传入局部变量表中等操作,然后将栈帧入栈,执行其中的代码,然后栈帧出栈,返回到调用该方法的地方。
可以看出来,执行equals方法,都要入栈出栈,花销要比 == 运算符大很多。而HashTable中可能有非常多的元素,在这样的场景下,每一次比较的性能优化累积下来,性能就能提高很多。
- 前面只说了原因,那么在重写equals方法的时候如何重写hashcode呢
参考文章https://blog.csdn.net/lihui6636/article/details/49470741
体会:多看源码和源码中的注释,可以减少很多疑惑。源码中都说明了方法的作用,比在网上查看别人写的资料要靠谱一点