hashCode 与 equals
hashCode和equals方法的关系 面试官可能会问你:“你重写过 hashcode 和 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值必定相同。
我们举例说明,自定义一个 Student 类:
1. 只重写 equals() 方法,不重写 hashcode() 方法:
1 public class Student { 2 private String name; 3 private int age; 4 5 public Student(String name, int age) { 6 super(); 7 this.name = name; 8 this.age = age; 9 } 10 @Override 11 public boolean equals(Object obj) { 12 if (this == obj) 13 return true; 14 if (obj == null) 15 return false; 16 if (getClass() != obj.getClass()) 17 return false; 18 Student other = (Student) obj; 19 if (age != other.age) 20 return false; 21 if (name == null) { 22 if (other.name != null) 23 return false; 24 } else if (!name.equals(other.name)) 25 return false; 26 return true; 27 } 28 29 public String getName() { 30 return name; 31 } 32 33 public void setName(String name) { 34 this.name = name; 35 } 36 37 public int getAge() { 38 return age; 39 } 40 41 public void setAge(int age) { 42 this.age = age; 43 } 44 }
执行一下测试类:
1 public class hashTest { 2 public static void test() { 3 Student stu1 = new Student("Gabit",21); 4 Student stu2 = new Student("Gabit",21); 5 6 System.out.println("两位同学是同一个人吗?"+stu1.equals(stu2)); 7 System.out.println("stu1.hashCode() = "+stu1.hashCode()); 8 System.out.println("stu1.hashCode() = "+stu2.hashCode()); 9 } 10 11 public static void main(String[] args) { 12 test(); 13 } 14 15 }
查看运行结果:
1 两位同学是同一个人吗?true 2 stu1.hashCode() = 1163157884 3 stu1.hashCode() = 1956725890
如果重写了 equals() 而未重写 hashcode() 方法,可能就会出现两个没有关系的对象 equals 相同(因为equal都是根据对象的特征进行重写的),但 hashcode 不相同的情况。因为此时 Student 类的 hashcode 方法就是 Object 默认的 hashcode方 法,由于默认的 hashcode 方法是根据对象的内存地址经哈希算法得来的,所以 stu1 != stu2,故两者的 hashcode 值不一定相等。
根据 hashcode 的规则,两个对象相等其 hash 值一定要相等,矛盾就这样产生了。上面我们已经解释了为什么要使用 hashcode 算法,所以即使字面量相等,但是产生两个不同的 hashCode 值显然不是我们想要的结果。
2. 如果我们在重写 equals() 时,也重写了 hashCode() 方法:
1 public class Student { 2 private String name; 3 private int age; 4 5 public Student(String name, int age) { 6 super(); 7 this.name = name; 8 this.age = age; 9 } 10 11 @Override 12 public int hashCode() { 13 final int prime = 31; 14 int result = 1; 15 result = prime * result + age; 16 result = prime * result + ((name == null) ? 0 : name.hashCode()); 17 return result; 18 } 19 20 @Override 21 public boolean equals(Object obj) { 22 if (this == obj) 23 return true; 24 if (obj == null) 25 return false; 26 if (getClass() != obj.getClass()) 27 return false; 28 Student other = (Student) obj; 29 if (age != other.age) 30 return false; 31 if (name == null) { 32 if (other.name != null) 33 return false; 34 } else if (!name.equals(other.name)) 35 return false; 36 return true; 37 } 38 39 public String getName() { 40 return name; 41 } 42 43 public void setName(String name) { 44 this.name = name; 45 } 46 47 public int getAge() { 48 return age; 49 } 50 51 public void setAge(int age) { 52 this.age = age; 53 } 54 }
执行结果:
1 两位同学是同一个人吗?true 2 stu1.hashCode() = 71578563 3 stu1.hashCode() = 71578563
从 Student 类重写后的 hashcode() 方法中可以看出,重写后返回的新的 hash 值与 Student 的两个属性是有关,这样就确保了对象和对象地址之间的关联性。
一句话:实现了“两个对象 equals 相等,那么地址也一定相同”的概念!
最后附上大佬的原文链接:https://blog.csdn.net/weixin_44259720/article/details/88414828