equals()和hashCode()方法总结

在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个。在多数情况 下,这两个函数是不用考虑的,直接使用它们的默认设计就可以了。但是在一些情况下,这两个函数最好是自己设计,才能确保整个程序的正常运行。最常见的是当 一个对象被加入收集对象(collection object)时,这两个函数必须自己设计。更细化的定义是:如果你想...
摘要由CSDN通过智能技术生成

在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个。在多数情况 下,这两个函数是不用考虑的,直接使用它们的默认设计就可以了。

但是在一些情况下,这两个函数最好是自己设计,才能确保整个程序的正常运行。最常见的是当 一个对象被加入收集对象(collection object)时,这两个函数必须自己设计。更细化的定义是:如果你想将一个对象A放入另一个收集对象B里,或者使用这个对象A为查找一个元对象在收集对象B里位置的钥匙,并支持是否容纳,删除收集对象B里的元对象这样的操作,那么,equals()和hashCode()函数必须开发者自己定义。其他情 况下,这两个函数是不需要定义的。

equals()

它是用于进行两个对象的比较,是对象内容的比较,当然也能用于进行对象参阅值的比较。什么是对象参阅值的比较?就是两个参阅变量的值的比较,我们都知道参阅变量的值其实就是一个数字,这个数字可以看成是鉴别不同对象的代号。两个对象参阅值的比较,就是两个数字的比较,两个代号的比较。这种比较是默认的对象比较方式,在Object这个对象中,这种方式就已经设计好了。所以你也不用自己来重写,浪费不必要的时间。

对象内容的比较才是设计equals()的真正目的,Java语言对equals()的要求如下,这些要求是必须遵循的。否则,你就不该浪费时间:

  • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

  • 反射性:x.equals(x)必须返回是“true”。

  • 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

  • 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

    任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

hashCode()

这个函数返回的就是一个用来进行哈希操作的整型代号,请不要把这个代号和前面所说的参阅变量所代表的代号弄混了。后者不仅仅是个代号还具有在内存中才查找对象的位置的功能。hashCode()所返回的值是用来分类对象在一些特定的收集对象中的位置。这些对象是HashMap, Hashtable, HashSet,等等。这个函数和上面的equals()函数必须自己设计,用来协助HashMap, Hashtable, HashSet,等等对自己所收集的大量对象进行搜寻和定位。

这些收集对象究竟如何工作的,想象每个元对象hashCode是一个箱子的编码,按照编码,每个元对象就是根据hashCode()提供的代号归入相应的箱子里。所有的箱子加起来就是一个HashSet,HashMap,或 Hashtable对象,我们需要寻找一个元对象时,先看它的代码,就是hashCode()返回的整型值,这样我们找到它所在的箱子,然后在箱子里,每 个元对象都拿出来一个个和我们要找的对象进行对比,如果两个对象的内容相等,我们的搜寻也就结束。这种操作需要两个重要的信息,一是对象的 hashCode(),还有一个是对象内容对比的结果。

官方文档定义

hashCode 的常规协定是:
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不是必需:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

以上这段官方文档的定义,我们可以抽出成以下几个关键点:

  1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
  2. 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
  3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
  4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

在简单一点就是,hashCode()的返回值和equals()的关系如下:

  1. 如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
  2. 如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

为什么这两个规则是这样的,原因其实很简单,拿HashSet来说吧,HashSet可以拥有一个或更多的箱子,在同一个箱子中可以有一个或更多的独特元对象(HashSet所容纳的必须是独特的元对象)。这个例子说明一个元对象可以和其他不同的元对象拥有相同的hashCode。但是一个 元对象只能和拥有同样内容的元对象相等。所以这两个规则必须成立。

设计这两个函数所要注意到的:

  • 如果你设计的对象类型并不使用于收集性对象,那么没有必要自己再设计这两个函数的处理方式。这是正确的面向对象设计方法,任何用户一时用不到的功能,就先不要设计,以免给日后功能扩展带来麻烦。
  • 如果你在设计时想别出心裁,不遵守以上的两套规则,那么劝你还是不要做这样想入非非的事。我还没有遇到过哪一个开发者和我说设计这两个函数要违背前面说的两个规则,我碰到这些违反规则的情况时,都是作为设计错误处理。

当一个对象类型作为收集型对象的元对象时,这个对象应该拥有自己处理equals(),和/或处理hashCode()的设计,而且要遵守前面所说的两种原则。equals()先要查null和是否是同一类型。查同一类型是为了避免出现ClassCastException这样的异常给丢出来。查 null是为了避免出现NullPointerException这样的异常给丢出来。

如果你的对象里面容纳的数据过多,那么这两个函数 equals()和hashCode()将会变得效率低。如果对象中拥有无法serialized的数据,equals()有可能在操作中出现错误。想象一个对象x,它的一个整型数据是transient型(不能被serialize成二进制数据流)。然而equals()和hashCode()都有依靠这个整型数据,那么,这个对象在serialization之前和之后,是否一样?答案是不一样。因为serialization之前的整型数据是有效的数据,在serialization之后,这个整型数据的值并没有存储下来,再重新由二进制数据流转换成对象后,两者(对象在serialization 之前和之后)的状态已经不同了。这也是要注意的。

为啥要用HashCode

Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。 equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。 于是,Java采用了哈希表的原理。

哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。

(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;

(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;

(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

从Object角度看,JVM每new一个Object,它都会将这个Object丢到一个Hash表中去,这样的话,下次做Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。若HashCode相同再去调用equal。

为啥重写了equals(),还要重写hashCode()

hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。

如果你学过数据结构就应该知道,在查找和排序这一章有hash查找

例如内存中有这样的位置

01234567

我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。

但是如果两个类有相同的hashcode怎么办呢(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

示例

最后,我们来看一个具体的示例吧,

public class HashTest {
	private int i;
	
public int getI() {
	return i;
}
 
public void setI(int i) {
	this.i = i;
}
 
public int hashCode() {
	return i % 10;
}
 
public final static void main(String[] args) {
	HashTest a = new HashTest();
	HashTest b = new HashTest();
	a.setI(1);
	b.setI(1);
	Set<HashTest> set = new HashSet<HashTest>();
	set.add(a);
	set.add(b);
	System.out.println(a.hashCode() == b.hashCode());
	System.out.println(a.equals(b));
	System.out.println(set);
}

}

这个输出的结果:
true
false

以上这个示例,我们只是重写了hashCode方法,从上面的结果可以看出,虽然两个对象的hashCode相等,但是实际上两个对象并不是相等;,我们没有重写equals方法,那么就会调用object默认的equals方法,是比较两个对象的引用是不是相同,显示这是两个不同的对象,两个对象的引用肯定是不定的。这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象a,b都被放到了HashSet中,这样HashSet就失去了他本身的意义了。

此时我们把equals方法给加上:

public class HashTest {
	private int i;

public int getI() {
	return i;
}
 
public void setI(int i) {
	this.i = i;
}
 
public boolean equals(Object object) {
	if (object == null) {
		return false;
	}
	if (object == this) {
		return true;
	}
	if (!(object instanceof HashTest)) {
		return false;
	}
	HashTest other = (HashTest) object;
	if (other.getI() == this.getI()) {
		return true;
	}
	return false;
}
 
public int hashCode() {
	return i % 10;
}
 
public final static void main(String[] args) {
	HashTest a = new HashTest();
	HashTest b = new HashTest();
	a.setI(1);
	b.setI(1);
	Set<HashTest> set = new HashSet<HashTest>();
	set.add(a);
	set.add(b);
	System.out.println(a.hashCode() == b.hashCode());
	System.out.println(a.equals(b));
	System.out.println(set);
}

}
此时得到的结果就会如下:
true
true

从结果我们可以看出,现在两个对象就完全相等了,HashSet中也只存放了一份对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值