详解hashcode和equals
1. equals 和 == 的区别,hashcode 是什么
java.lang.Object 类中提供了一些基本的方法
/** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}. * <p> * The general contract of {@code hashCode} is: * <ul> * <li>Whenever it is invoked on the same object more than once during * an execution of a Java application, the {@code hashCode} method * must consistently return the same integer, provided no information * used in {@code 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. * <li>If two objects are equal according to the {@code equals(Object)} * method, then calling the {@code hashCode} method on each of * the two objects must produce the same integer result. * <li>It is <em>not</em> required that if two objects are unequal * according to the {@link java.lang.Object#equals(java.lang.Object)} * method, then calling the {@code 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. * </ul> * <p> * As much as is reasonably practical, the hashCode method defined by * class {@code Object} does return distinct integers for distinct * objects. (This is typically implemented by converting the internal * address of the object into an integer, but this implementation * technique is not required by the * Java™ programming language.) * * @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */ public native int hashCode();
/** * Indicates whether some other object is "equal to" this one. * <p> * The {@code equals} method implements an equivalence relation * on non-null object references: * <ul> * <li>It is <i>reflexive</i>: for any non-null reference value * {@code x}, {@code x.equals(x)} should return * {@code true}. * <li>It is <i>symmetric</i>: for any non-null reference values * {@code x} and {@code y}, {@code x.equals(y)} * should return {@code true} if and only if * {@code y.equals(x)} returns {@code true}. * <li>It is <i>transitive</i>: for any non-null reference values * {@code x}, {@code y}, and {@code z}, if * {@code x.equals(y)} returns {@code true} and * {@code y.equals(z)} returns {@code true}, then * {@code x.equals(z)} should return {@code true}. * <li>It is <i>consistent</i>: for any non-null reference values * {@code x} and {@code y}, multiple invocations of * {@code x.equals(y)} consistently return {@code true} * or consistently return {@code false}, provided no * information used in {@code equals} comparisons on the * objects is modified. * <li>For any non-null reference value {@code x}, * {@code x.equals(null)} should return {@code false}. * </ul> * <p> * The {@code equals} method for class {@code Object} implements * the most discriminating possible equivalence relation on objects; * that is, for any non-null reference values {@code x} and * {@code y}, this method returns {@code true} if and only * if {@code x} and {@code y} refer to the same object * ({@code x == y} has the value {@code true}). * <p> * Note that it is generally necessary to override the {@code hashCode} * method whenever this method is overridden, so as to maintain the * general contract for the {@code hashCode} method, which states * that equal objects must have equal hash codes. * * @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */ public boolean equals(Object obj) { return (this == obj); }
hashcode 和 equals方法都不是final类型的,因此可以被继承类重写,在Object中 equals方法 最终也是通过 == 来比较的,这里equals只能比较两个对象的内存地址,如果想比较两个对象的其他信息是否相等,就要重写equals方法。另外我们从这个方法的注释来看,jdk规定了实现该方法应该遵循的规定:
(1)自反性:x.equals(x)必须返回true。
(2)对称性:x.equals(y)与y.equals(x)的返回值必须相等。
(3)传递性:x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。
(4)一致性:如果对象x和y在equals()中使用的信息都没有改变,那么x.equals(y)值始终不变。
(5)非null:x不是null,y为null,则x.equals(y)必须为false。
hashcode是什么:
java.lang.Object 中的hashcode 是将对象在内存中的地址作为哈希码返回,jvm堆内存中不同位置的对象的哈希码一定会不同,其实hashcode真正使用到的地方是散列表对象,例如HashMap, HashTable, HashSet,hashcode获取对象的散列码,来确定当前对象在散列表中的位置
和equals一样,重写hashcode也要遵循相关的规定:
(1) 如果两个对象通过equals方法比较,返回结果是true相等,那么他们的hashcode值一定相等
(2) 如果两个对象通过equals方法比较,返回结果是false不相等,那么他们的hashcode值可以不相等,也可以相等
(3) 如果两个对象的hashcode值相等,那么通过equals比较的结果不一定相等
(4) 如果两个对象的hashcode值不相等,那么通过equals比较的结果一定不相等
2. 为什么 重写equals 方法后,一定要重写hashcode方法
首先看一段代码:
public class jdkTest { public static void main(String[] args) { // 新建Person对象, User p1 = new User("啊啊啊", 26); User p2 = new User("啊啊啊", 26); User p3 = new User("发发发", 24); // 新建HashSet对象 HashSet set = new HashSet(); set.add(p1); set.add(p2); set.add(p3); // 比较p1 和 p2, 并打印它们的hashCode() System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode()); // 打印set System.out.printf("set:%s\n", set); } private static class User { int age; String name; public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } /** * @desc 覆盖equals方法 */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } //判断是否类型相同 if (this.getClass() != obj.getClass()) { return false; } User person = (User)obj; return name.equals(person.name) && age == person.age; } } }
看一下执行结果:
从上图中可以看出,虽然p1.equals(p2) 的结果为true,即p1对象和p2对象相等,他们的hashcode不相等,set中会存在两条重复的对象数据,这和我们所了解的java.util.Set中 存放的数据一定不重复 产生冲突了。想要知道为什么此时set中会存在重复数据,就必须先了解一下HashSet中add方法的原理:
由此可见,HashSet中add方法最终调用的是HashMap的put方法,再研究一下HashMap的put方法:
由jdk中最终的putval方法可知,在比较两个Node<K,V>是否相等的时候,首先会比较两者的hashcode是否相等,如果两者的hashcode不相等,那么就会默认这两个Node<K,V>不相等,所以在HashSet中放入了两个重复的对象,所以在重写hashcode和equals的时候一定要遵循上面所介绍的几个原则,不然会导致意外情况发生。
3. 既然equals能够比较两个对象,为什么还需要hashcode
我们知道,通过重写equals方法可以很好的用来比较两个对象,但是这种比较的效率是比较低的,如果我们在比较的过程中,首先使用hashcode来比较,如果hashcode都不一样,那么两个对象肯定不相等,如果hashcode相等,然后再使用equals来比较,如果equals也相等,那么两个对象肯定相等,这样就能大大的提高比较的效率和比较的正确性。
4. 如何重写 equals 和 hashcode
hashcode重写:
《Effective Java》中提出了一种简单通用的hashCode算法:
A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:
(1) 如果是boolean值,则计算f ? 1:0
(2) 如果是byte\char\short\int,则计算(int)f
(3) 如果是long值,则计算(int)(f ^ (f >>> 32))
(4) 如果是float值,则计算Float.floatToIntBits(f)
(5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
(6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
(7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。
C、最后,把每个域的散列码合并到对象的哈希码中。
private static class User { int age; String name; public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } /** * @desc 覆盖equals方法 */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } //判断是否类型相同 if (this.getClass() != obj.getClass()) { return false; } User person = (User)obj; return name.equals(person.name) && age == person.age; } @Override public int hashCode() { int hash = 17; hash = hash * 31 + name.hashCode(); hash = hash * 31 + age; return hash; } }