熟悉Object类的同学都知道,equals()和hashCode()都是Object类中的方法。本篇文章为大家详细介绍equals()和hashCode()的用途,以及equals()和hashCode()联系与区别,
其中equals()方法我们经常用到,比如String对象的比较str1.equals(str2)。两个方法本来没有任何耦合关系,但是把它和hashCode()方法放在一起总结是因为Map集合的实现让这两个方法产生了某种程度上的耦合。
hashCode()的作用是获取哈希码,也称为散列码。它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
为了更好的理解,我们可以在脑海中构建一个场景:我们要去除一组数中重复的值,保留不相同的值。我们首先遍历,然后比较他们如果有相同的就干掉,普通的方式还得循环嵌套。如果把这组数换成一组对象的话,我们又如何去掉相同对象,内容相同的只保留一个呢。如果我们按照之前的方法,就得遍历数组,然后用get方法获取对象中的值,依次比较。这种方式不仅很蠢而且很慢,我们能不能像比较数字那样去比较对象呢?只要我们保证内容相同的对象生成相同的整数就好了,这样就好比较了,这个整数就是hashCode。
hashCode会出现冲突,什么叫冲突,就是我们不能保证不同的对象生成的不同的hashCode,有可能不同的对象生成的hashCode是相同的,这时候我们怎么区别呢,那么我们就需要比较对象的所有字段了,就是重写equals方法,在equals方法中去比较两个对象的所有字段。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
由于每一个 Java 类都继承自 Object 类,所以每一个对象都具有equals这个方法。Object 类中定义的 equals(Object)方法是直接使用「==」运算符比较的两个对象,所以在没有覆盖 equals(Object)方法的情况下,equals(Object)与「==」运算符一样,比较的是引用。相比「==」运算符,equals(Object)方法的特殊之处就在于它可以被覆盖,所以可以通过覆盖的方法让它不是比较引用而是比较数据内容,例如 String 类的 equals 方法是用于比较两个独立对象的内容是否相同,即堆中的内容是否相同。
public static void main(String[] args) {
String str1 = new String("Hello world");
String str2 = new String("Hello world");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
}
实际情况下我们是可以根据str2来找到散列表中key为str1的元素的,如果只使用==比较来判断key是否匹配是不可行的,因为他们在堆上的地址并不一样,所以得使用equals()来进一步比较。 equals()在Object中默认的实现其实也是使用==比较:
public boolean equals(Object obj) {
return (this == obj);
}
之所以我们可以直接使用String的equals(),是因为String类重写了equals(),具体实现就是比较两个字符串对象底层的char数组存放的字符是否完全一样。所以在我们要比较自定义的类的对象是否相等的时候,我们要根据自己的需求主动重写equals(),重写equals()也是我们能够把自定义的类作为散列容器中key的类型的先决条件。
equals() 和 hashCode()的关系:
Effective Java 3rd edition
Item 11 : Always override hashCode when you override equals
从HashMap的源码得出,当从map中找一个指定的key时,我们首先是根据这个key的hashCode()的返回值经过处理后生成的hash值找到对应的桶,再遍历这个桶找到相等的key。
从以上过程可以提炼出非常重要的一点,相等的对象hashCode()返回值一定要相等,因为如果hashCode()返回值不相等的话,计算出来的hash值很可能是不相等的,这会直接导致相同的key可能放入不同的桶中,这是不被允许的。所以如果我们要用自定义类对象做散列表的key时,在重写了equals()后,还要重写hashCode()来保证equals()返回为true的情况下两个对象hashCode()的返回值也相等。
实例分析:
public class Test {
public static void main(String[] args) {
HashMap map = new HashMap<>();
// 生成两个相等的对象
People p1 = new People(11, "Jack");
People p2 = new People(11, "Jack");
map.put(p1, 1);
// 当没重写hashCode()时输出的是null
// 重写时输出的是 1
System.out.println(map.get(p2));
}
}
// 自定义People类
class People {
int age;
String name;
public People(int age, String name) {
this.age = age;
this.name = name;
}
// 我们认为只要两个People对象age和name相等则这两个对象相等
@Override
public boolean equals(Object obj) {
return ((People) obj).age == age && ((People) obj).name.equals(name);
}
// equals()返回为true时,两个对象的hashCode()返回值也是一样的
@Override
public int hashCode() {
return age * name.hashCode();
}
}
为了避免在使用散列容器出现错误,当我们重写自定义类的equals()方法时,还要重写hashCode()方法,以保证相等的对象hashCode()返回值必定相等。hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
既然hashCode()效率这么高为什么还要equals()呢?因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出结论:equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
hashCode和equals之间的区别在java编程实战中很容易遇见的问题,也是java面试题中的高频问题。无论是为了学习还是工作,我们都应该也必须要明辨hashCode和equals的用法差异。差之毫厘,谬以千里,看似相同的两者在实际中如果用混了可能会造成整个程序的错误,使得程序无法运行。
QCode09-04 14:38
Code大师09-04 14:50
不写代码你养我啊08-23 11:14
不写代码你养我啊09-17 18:02
要学习了06-18 18:13