本文从下面几个方面来分析:
1.equals() 的作用是什么?
2.equals() 与 == 的区别是什么?
3.hashCode() 的作用是什么?
4.hashCode() 和 equals() 之间有什么联系?
第1部分 equals() 的作用
equals() 的作用是 用来判断两个对象是否相等。
- equals() 定义在JDK的Object.java中,通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。在Object.java中的源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
既然这个方法在Object中,这就意味着所有的对象都有这个方法,从源码中可以看出,默认的equals()与“==”是等价的。因此,我们通常会覆写equals()方法:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。 就是说:
1) 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。
2) 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。
第2部分 equals() 与 == 的区别是什么?
== : 它的作用是判断两个对象的内存地址(或说引用)地址是不是相等。即,判断两个对象是不是同一个对象(对于基本数据类型,如int,比较的值;对于引用类型,如Integer,比较的是引用地址,就是说它们是否指向同一个对象)。
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况(前面第1部分已详细介绍过):
情况1,类没有覆写equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2,类覆盖了equals()方法。一般,我们通过覆写equals()方法来判断两个对象的内容是否相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。典型的覆盖内容的equals()写法:
private static class Person {
int age;
String name;
public String toString() {
return "(" + name + ", " + age + ")";
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
//如果是同一个对象返回true,反之返回false
if (this == obj) {
return true;
}
//判断是否类型相同
if (this.getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return name.equals(person.name) && age == person.age;
}
}
第3部分 hashCode() 的作用
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
虽然,每个Java类都包含hashCode() 函数。但是,仅仅当创建并某个“类的散列表”时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。
上面的散列表,指的是Java集合框架中有关散列表的类,如HashMap,Hashtable,HashSet。
也就是说:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。从HashMap.put()源码很容易看出来:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>>
16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab =
table) ==
null || (n = tab.length) ==
0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//....限于篇幅这里省略部分源码,
//但不影响问题的分析
}
++modCount;
if (++size >
threshold)
resize();
afterNodeInsertion(evict);
return null;
}
第4部分 hashCode() 和 equals() 的关系
按照“类的用途”分两种情况
情况一、类不使用在散列表的数据结构中,如:HashMap、HashSet等
这种情况,该类的hashCode()与equals()没有任何半毛关系,就是说equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用理会hashCode()
情况二、类的对象用在了类似HashMap、HashSet这样的散列表数据结构中
在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
1)、如果两个对象相等,那么它们的hashCode()值一定相同。
这里的相等是指,通过equals()比较两个对象时返回true(再次强调说明:equals()返回true,意味着要么指向的是同一个对象,要么内容一致)。
2)、如果两个对象hashCode()相等,它们并不一定相等。
因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值可能相等”,这就是哈希冲突
(恶补:为什么会有产生冲突?简单的说,hash法是一种映射算法,一种压缩算法,很简单的一个场景,试想一下,你们班上有30个同学,其中只有5个女生,这不是僧多粥少吗?一个萝卜能有一个坑吗?能不冲突吗?!)。
此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。
如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。
总之:{obj1.equals(obj2)}==》{(obj1.hashCode()) == obj2.hashCode()},对象相等与对象的hashCode相等是充分非必要条件
效果分析:
import java.util.HashSet;
/**
*
* @author java栈长
* @desc 比较equals() 返回true 或 false时, hashCode()的值。
* <p>
* debug时,注释Person类的hashCode(),查看输出结果:
* <p>
* 一、不覆写hashCode(): //debug时注释hashCode()即可
* 输出
* p1.equals(p2) : true; p1(1174361318) p2(589873731)
* set:[(eee, 100), (eee, 100), (aaa, 200)] //hashSet中出现重复元素,因为p1和p2的hashCode()不相同
* <p>
* 二、覆写Person类的hashCode() ------>正确姿势
* 输出:
* p1.equals(p2) : true; p1(37) p2(37)
* set:[(a, 100), (b, 200)] //hashSet中没有重复元素,p1和p2的hashCode相同了,equals()也生效了。
*/
public class HashCodeTest {
public static void main(String[] args) {
// 新建Person对象,
Person p1 =
new Person("a", 100);
Person p2 =
new Person("a", 100);
Person p3 =
new Person("b", 200);
// 新建HashSet对象
HashSet set =
new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);
// 比较p1 和 p2, 并打印它们的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String
name;
@Override
public int hashCode() {
int nameHash =
name.toUpperCase().hashCode();
return nameHash ^
age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String
toString() {
return "(" +
name +
", " +
age +
")";
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj) {
if (obj ==
null) {
return false;
}
//如果是同一个对象返回true,反之返回false
if (this == obj) {
return true;
}
//判断是否类型相同
if (this.getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return name.equals(person.name) &&
age == person.age;
}
}
}
输出结果分析:
一、不覆写hashCode(): //debug时注释hashCode()即可
输出:
p1.equals(p2) : true; p1(1174361318) p2(589873731)
set:[(eee, 100), (eee, 100), (aaa, 200)] //hashSet中出现重复元素,因为p1和p2的hashCode()不相同,hashSet中hashCode不的
思考:p1和p2的hashCode为什么不同,jvm是如何生成这个hashCode的?
请移步:《java类的hashCode》一文
二、覆写hashCode() ------>正确姿势
输出:
p1.equals(p2) : true; p1(37) p2(37)
set:[(a, 100), (b, 200)] //hashSet中没有重复元素,p1和p2的hashCode相同了,equals()也生效了。
更多干货在公号【java栈长】!