为什么重写 equals 时候,必须重写 hashCode?

为什么重写 equals 时候,必须重写 hashCode?

今天看到一面试题,写的是:”为什么重写 equals 时候,必须重写 hashCode?“。当时我整个人都精神了,这不又可以写一篇博客了么。

咱首先说说 equals() 它存在于 Object 类中。我们先看看代码

public boolean equals(Object obj) {return (this == obj);}

其实它默认的还是使用 "==" 来做判断相等,这样跟没有使用这个有什么区别咧?既然咱提到了 "==" 不如先说说它。"==" 比较的是两个对象的地址是否相等(基本类型比较它的值,引用类型比较它的内存地址)例如下面的代码

// Student.java
public class Student {
	private String name;
	private Integer age;

	public String getName() {return name;}

	public void setName(String name) {this.name = name;}

	public Integer getAge() {return age;}

	public void setAge(Integer age) {this.age = age;}
}
// Main.java
public static void main(String[] args) {
		
		Student first = new Student();
		first.setAge(18);
		first.setName("张三");
		System.out.println(first); 
		Student second = new Student();
		second.setAge(18);
		second.setName("张三");
		System.out.println(second);
		if(first == second) {
			System.out.println(">>> 通过 == 方法,first 与 second 相等");
		}else {
			System.out.println(">>> 通过 == 方法,first 与 second 不相等");
		}
  
		if(first.equals(second)) {
			System.out.println(">>> 通过 equal 方法,first 与 second 相同");
		}else {
			System.out.println(">>> 通过 equal 方法,first 与 second 不相同");
		}
}

按道理说我两个实例都是相同的人相同的年龄应该是相等的,但返回的是

>>> 通过 == 方法,first 与 second 不相等
>>> 通过 equal 方法,first 与 second 不相等

所以说它们两个虽然内容相同但所存储的地址并不同,毕竟我们创建的两个实例。既然我们修改不了 "==",我们就来重写 equals() ,就像是 String 类也重写了这个方法一样,在 Student 类中重写 equals 方法

  @Override
	public boolean equals(Object obj) {
		if (obj instanceof Student) {
			Student tmp = (Student) obj;
			if (this == tmp) {
				return true;
			}

			if (tmp.name.equals(this.name) && tmp.age.equals(age)) {
				return true;
			}
		}
		return false;

	}

运行 Main 方法就会得到下面结果

>>> 通过 == 方法,first 与 second 不相等
>>> 通过 equal 方法,first 与 second 相同

这样就能使得两个对象相等了。还有一个要说的是 JDK 对 equals 的要求,

It is reflexive: for any non-null reference value x, x.equals(x) should return true.

反射性:对于任何的非空参考值x.则 x.equal(x)返回 true

It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

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

It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

它是可传递的:对于任何非空的引用值xyz,如果x.equals(y)返回truey.equals(z)返回true,那么x.equals(z)应该返回true

It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

一致性:对于任何非空的引用值x和y,只要不修改对象的equals比较中使用的信息,x.equals(y)的多次调用都会始终返回true或一致返回false。

For any non-null reference value x, x.equals(null) should return false

对于任何非空的引用值xx.equals(null) 应该返回false

这是基于刚才写的 equal 代码写的,符合要求。


	public static void main(String[] args) {

		Student first = new Student();
		first.setAge(18);
		first.setName("张三");
    
		Student second = new Student();
		second.setAge(18);
		second.setName("张三");

		Student third = new Student();
		third.setAge(18);
		third.setName("张三");

		// 反射性
		System.out.println(first.equals(first));// true
		// 对称性
		System.out.println(first.equals(second));// true
		System.out.println(second.equals(first));// true
		// 传递性
		System.out.println(second.equals(third));// true
		System.out.println(first.equals(third));// true
		// 一致性
		System.out.println(first.equals(second));// true
		System.out.println(first.equals(second));// true
		System.out.println(first.equals(second));// true
		System.out.println(first.equals(second));// true
		System.out.println(first.equals(second));// true
		// 非空性
		System.out.println(first.equals(null));// false
		System.out.println(second.equals(null));// false
		System.out.println(third.equals(null));// false

	}

下面来说说 hashCode ,它的作用是获取哈希码并返回一个 Int 类型的整数,为了确定对象在哈希表中的位置。这个方法也是存在于 Object 中,所以每个类也都包含这个方法

public native int hashCode();

所以说它的作用是在散列表中才有用,在其它状况下是没有用的。 来让我用 HashSet 来测试一下这个 hashCode 的作用。

直接在 Main 方法中写一个 hashset 并存入两组数据

public static void main(String[] args) {

		Student first = new Student();
		first.setAge(18);
		first.setName("张三");
		System.out.println(first);
  
		Student second = new Student();
		second.setAge(18);
		second.setName("张三");
		System.out.println(second);
  
		HashSet<Student> set = new HashSet<Student>();
		set.add(first);
		set.add(second);

		System.out.println(set);

	}

在 Student 类重写 toString 方法,方便展示信息

@Override
	public String toString() {
		return String.format("学生姓名:%s|年龄:%d|HashCode:%d|Class:%s", name, age, this.hashCode(),
				this.getClass().toString());
	}

返回结果

学生姓名:张三|年龄:18|HashCode:2018699554|Class:class idea.rewritetest.Student
学生姓名:张三|年龄:18|HashCode:356573597|Class:class idea.rewritetest.Student

[学生姓名:张三|年龄:18|HashCode:2018699554|Class:class idea.rewritetest.Student, 
学生姓名:张三|年龄:18|HashCode:356573597|Class:class idea.rewritetest.Student]

虽然两个实例的内容都是一样的,但是 HashSet 还是存入了两个。这就是因为 HashSet 判断了两个实例的 hashCode,两个实例的 hashCode 使用的是 Object 的 hashCode方法,所以返回的是系统的 hashCode 值。这不是我想要的。所以要自己写一个 hashCode。

@Override
public int hashCode() {
	return name.hashCode();
}

在 Student 类重写 hashCode 方法,也就是以上的代码。然后运行之后得到的结果

学生姓名:张三|年龄:18|HashCode:774889|Class:class idea.rewritetest.Student
学生姓名:张三|年龄:18|HashCode:774889|Class:class idea.rewritetest.Student

[学生姓名:张三|年龄:18|HashCode:774889|Class:class idea.rewritetest.Student]

这样两个对象加入 HashSet 时,由于我们重新写了 hashCode。当有相同的 姓名 的时候就会有相同的 hashCode 值,存入的学生信息就不会重复了,当然这里没有考虑如果学生中有同名怎么办,其实在加上其它条件返回

hashCode就好了。然后说说为什么重写 equals 必须要要重新写 hashCode,首先 JDK 中的规定

Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

只要在执行Java应用程序时多次在同一个对象上调用该方法, hashCode方法必须始终返回相同的整数,前提是修改了对象中equals比较中的信息。 该整数不需要从一个应用程序的执行到相同应用程序的另一个执行保持一致。

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

如果根据equals(Object)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。

It is not required that if two objects are unequal according to the java.lang.Object.equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

不要求如果两个对象根据equals(java.lang.Object)方法不相等,那么在两个对象中的每个对象上调用hashCode方法必须产生不同的整数结果。 但是,程序员应该意识到,为不等对象生成不同的整数结果可能会提高哈希表的性能。

如果重写了 equals 之后不去重新写 hashCode 那么即使通过 equals 判断得到了两个值相等它们的哈希值也不会相等。所以重写了 equals 必须要要重新写 hashCode。

以上文章为我的见解。请大家原谅一些没有写清楚的地方。

# Java 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×