为什么重写equals()方法时,必须要求重写hashCode()方法?

equals() 方法和 hashcode() 方法是 java.lang.Object 类的两个重要的方法,Java中多数类都会重写 Object 类的 equals() 方法。

在实际应用中,如果我们自定义的类需要进行比较操作,就一定也需要重写 equals() 方法。那么为什么重写 equals()方法时,必须要求重写 hashCode() 方法呢?

首先, equals() 方法和 hashcode() 方法间的关系是这样的:

1、如果两个对象相同(即:用 equals 比较返回true),那么它们的 hashCode 值一定要相同;

2、如果两个对象的 hashCode 相同,它们并不一定相同(即:用 equals 比较返回 false);

上面这两句话,如果明白【散列表】的结构,就一定会很明白,这里只简单提一句:散列表同时运用了数组和链表。

《Effective java》一书中这样说到:在每个覆盖了 equals() 方法的类中,也必须覆盖 hashCode() 方法,如果不这样做的话,就会违反 Object.hashCode 的通用的约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap,HashSet 和 HashTable。

先解释下,为什么一定要使用 hashcode() 方法:

归根结底就是为了提高程序的效率才实现了 hashcode() 方法。

程序先进行 hashcode 的比较,如果不同,那没就不必在进行 equals 的比较了,这样就大大减少了 equals 比较的次数,这对比需要比较的数量很大的效率提高是很明显的,一个很好的例子就是在集合中的使用:

  • 我们都知道java中的List集合是有序的,因此是可以重复的,而set集合是无序的,因此是不能重复的。那么怎么能保证不能被放入重复的元素呢?单靠 equals() 方法比较的话,如果原来集合中有10000个元素,那么放入第10001个元素时,难道要将前面的所有元素都进行比较,看看是否有重复?这个效率可想而知。
  • 因此 hashcode 就应遇而生了,java 就采用了 hash 表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的 hashcode 地址上是否有值,那么就用equals 比较,如果没有则直接插入,这样就大大减少了 equals 的使用次数,执行效率就大大提高了。

继续上面的话题,重写 hashcode() 方法的原因,简单的说就是:为了保证是同一个对象,在 equals 比较相同的情况下 hashcode值必定相同。

再来看看,重写 hashcode() 方法的影响:

我们举例说明,自定义一个 Student 类:

1. 只重写 equals() 方法,不重写 hashcode() 方法:

public class Student {
	private String name;
	private int age;

	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	// 省略 get,set方法...
}

我们执行下面的程序看看效果: 

public class hashTest {
	@Test
	public void test() {
		Student stu1 = new Student("Jimmy",24);
		Student stu2 = new Student("Jimmy",24);
		
		System.out.println("两位同学是同一个人吗?"+stu1.equals(stu2));
		System.out.println("stu1.hashCode() = "+stu1.hashCode());
		System.out.println("stu1.hashCode() = "+stu2.hashCode());
	}
}

执行结果:

两位同学是同一个人吗?true
stu1.hashCode() = 379110473
stu1.hashCode() = 99550389

如果重写了 equals() 而未重写 hashcode() 方法,可能就会出现两个没有关系的对象 equals 相同(因为equal都是根据对象的特征进行重写的),但 hashcode 不相同的情况。因为此时 Student 类的 hashcode 方法就是 Object 默认的 hashcode方 法,由于默认的 hashcode 方法是根据对象的内存地址经哈希算法得来的,所以 stu1 !=  stu2,故两者的 hashcode 值不一定相等。

根据 hashcode 的规则,两个对象相等其 hash 值一定要相等,矛盾就这样产生了。上面我们已经解释了为什么要使用 hashcode 算法,所以即使字面量相等,但是产生两个不同的 hashCode 值显然不是我们想要的结果。

2. 如果我们在重写 equals() 时,也重写了 hashCode() 方法:

public class Student {
	private String name;
	private int age;
	
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	// 省略 get,set方法...
}

执行结果:

两位同学是同一个人吗?true
stu1.hashCode() = 71578563
stu1.hashCode() = 71578563

从 Student 类重写后的 hashcode() 方法中可以看出,重写后返回的新的 hash 值与 Student 的两个属性是有关,这样就确保了对象和对象地址之间的关联性。

一句话:实现了“两个对象 equals 相等,那么地址也一定相同”的概念!

 

更多精彩,请关注我的"今日头条号":Java云笔记
随时随地,让你拥有最新,最便捷的掌上云服务

IT无知君 CSDN认证博客专家 处女座程序员 Java 大牛 GitHub
微信搜一搜「IT无知君」关注这个有意思的程序员,回复「资料」更有精心准备的一线大厂面试资料,技术书籍,简历模板;
博客中涉及的项目源码,已由「GitHub」(https://github.com/IamJiming) 收录,欢迎交流,感谢关注,持续更新...
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页