JAVA中的hashcode()和equals()

最近在探究JAVA中的equals方法时,发现很多地方都会强调如果你重写了equals方法就必须重写hashCode方法,对这一点感到比较迷惑,于是就去探究了一下关于hashcode和equals之间的关系,解决了这个疑惑点。

一、hashcode()

hashcode是jdk根据对象的地址或者字符串或数字算出来的int类型的数值,也就是哈希码,哈希码并不是完全唯一的。它的对应关系如下:

特点:一个对象只能对应一个确定的hashcode,但是一个hashcode可能会对应多个hashcode。

作用:在Java中,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。它可以减少equals的调用次数。当我们向集合中插入对象时,由于集合不允许重复的元素存在,我们首先需要判断集合里面是不是已经存在该对象了。通俗的做法就是用equals方法进行逐个比较,但是当这个集合特别大的时候,这种方法的效率无疑是很低的。而如果我们使用了hashcode。我们我们只需要比较hashcode的值即可。JAVA中的HashMap会用一个table保存每一个存进集合中对象的hashcode值。每当有对象要存进来时,先获取对象的hashcode值,如果table中不存在该hashcode,就直接把这个对象添加到集合中。如果存在这个hashcode,则把这个待插入的对象和集合中hashcode与它相同的对象进行equals操作,判断两个对象是否相同,不同在加入。如此一来,便可以大大减少equals的调用次数。

二、不同的对象的哈希码算法不同
1、Object类的hashCode()返回的哈希码是对象的内存地址经过处理后的整数值,由于每个对象的内存地址都不一样,所以哈希码也不一样。
2、String类的hashCode()返回的哈希码则与字符串的内容相关,只要字符串所在的堆空间相同,返回的哈希码也相同。
3、Integer类的hashCode()返回的哈希码就是Integer对象里所包含的那个整数的数值。因此,两个数值一样的Integer对象,返回的哈希码也一样。
小结:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的 字段等)映射成一个数值,这个数值称作为散列值。

三、equals方法

在Object中,equals方法的定义如下

会发现equals与==完全相同,都是比较两个引用的对象是不是同一个。

不同的类在继承Object类的时候都会根据自己的实际情况对equals进行重写。例如我们在前一篇博客中所说的String类,它就重写了equals方法。重写后的代码如下

它首先判断这两者的对象是否相同。即两个引用在栈中保存的对象地址是否相同,如果相同说明是同一个对象,直接返回true;如果不同,再做进一步判断。比较两个对象的字符串值是否完全相同。相同则返回true。

小结:

1、对于两个对象,如果equals()相等,hashCode()一定相等;但是如果hashCode()相等,equals()不一定相等。因为不同的对象可能映射到相同的hashcode,但是每个对象只能映射出一个确切的hashcode。

2、没有重写hashCode实例方法的前提下, 如果两个实例对象的hashCode相同,我们可以简单理解为两个对象就是同一个。

3、在如果重写了equals方法,则必须重写hashcode方法

四、为何很多地方都会强调如果重写了equals方法,就必须要重写hashcode方法?

其实一般情况下你如果重写了equals不去重写hashcode方法,程序也不会出什么问题。但是如果你的程序中用到了哈希表的结构,那就会有问题了。

1、测试样例一。代码如下:

//定义一个测试类
public class Testequals {
	public static class Node{
		public int number;
		public int data;
		
		public Node(int number,int data) {
			this.number=number;
			this.data=data;
		}
		
		public boolean equals(Node o) {
			if((o.data==this.data)&&(o.number==this.number)) return true;
			else return false;
		}
	}
	public static void main(String[] args) {
		String s1=new String("Alex");
		String s2=new String("Alex");
		System.out.println(s1.equals(s2));
	}
}

我们定义一个Node类,并重写了这个类的equals方法,如果两个引用的number和data都相同,我们就认为这两个引用相等。在这里我们实例化了两个对象,s1和s2两个引用分别引用一个对象。当我们调用equals方法时,显然会输出true。因为他们虽然引用了两个不同的对象,但是这两个对象的值相同。(跟字符串String的equals方法类似,比较的是存储在堆中的值,而不是存在栈中的引用)

2.测试样例二

import java.util.HashMap;

//定义一个测试类
public class Testequals {
	public static class Node{
		public int number;
		public int data;
		
		public Node(int number,int data) {
			this.number=number;
			this.data=data;
		}
		
		public boolean equals(Node o) {
			if((o.data==this.data)&&(o.number==this.number)) return true;
			else return false;
		}
	}
	public static void main(String[] args) {	
		Node node1=new Node(1,12);
		System.out.println(node1.hashCode());
		
		//构建一个哈希表
		HashMap<Node,Integer> map=new HashMap();
		//把当前对象存入哈希表
		map.put(node1, 3);
		
	        //从哈希表中取出相应的值
		System.out.println(map.get(new Node(1,12)));
	}
}

同样,重写Node类的equals方法,且没有重写它的hashcode方法。此时我们把Node对象作为关键值存入HashMap中,然后再通过Node对象往HashMap中查询相应的数据。运行结果如下

按照设想,程序应该会输出3,但是运行后输出的结果却是null。原因就出在Hashcode上。一开始我们存入HashMap中的key对象是node1,它是一个独立的对象,系统会给它生产一个hashcode。而我们在查询的时候用了new Node(1,12)这个语句,相当于又重新创建了一个对象,系统自然也会给这个对象生成属于它自己的一个hashcode。这两个对象的hashcode一般情况下是不同的。因此当我们在HashMap中查找数据时,程序首先会去存储hashcode的表中查找是否有和待查找的key相同的Hashcode,发现没有,于是返回null。这样子的话我们存入HashMap的值理论上永远也取不出来。

重写hashcode方法如下:

import java.util.HashMap;

//定义一个测试类
public class Testequals {
	public static class Node{
		public int number;
		public int data;
		
		public Node(int number,int data) {
			this.number=number;
			this.data=data;
		}
		
		public int hashCode() {
			return number+data;
		}
		
		public boolean equals(Node o) {
			if((o.data==this.data)&&(o.number==this.number)) return true;
			else return false;
		}
	}
	
	public static void main(String[] args) {	
		Node node1=new Node(1,12);
		System.out.println(node1.hashCode());
		
		//构建一个哈希表
		HashMap<Node,Integer> map=new HashMap();
		//把当前对象存入哈希表
		map.put(node1, 3);
		
		Node node2=new Node(1,12);
		System.out.println(node2.hashCode());
		
		//从哈希表中取出相应的值
		System.out.println(map.get(node2));
	}
}

小结一下关于JAVA中重写equals方法的规范

1.在程序执行起价,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回一个整数。

2.如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

3.如果两个对象根据equals方法比较是不等的,则hashCode方法不一定的=得返回不同的整数。

猜你喜欢

转载自blog.csdn.net/alexwym/article/details/81096530