hashCode和equals
hashCode() 和 equals() 有什么关系
情况一: 不会创建类对应的散列表
这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, HashTable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。
在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!equals() 用来比较该类的两个对象是否相等,而hashCode() 则根本没有任何作用,所以,不用理会hashCode()。
情况二: 会创建类对应的散列表
在HashSet, HashTable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,创建该类的HashSet集合
1 Object中的equals方法和“==”是相同的,如下代码,比较的都是内存地址
public boolean equals(Object obj) { return (this == obj); }
hashCode
原生的hashCode方法返回的是一个根据内存地址换算出来的一个值。它的定义是这样的
public native int hashCode();
通过将该对象的内部地址转换成一个整数来实现的,这个返回值就作为该对象的哈希码值返回。
2 在不重写equals和hashCode的情况下:
(1)两个对象如果equals相等的情况下,hashCode一定相等。因为equals默认是用“==”来比较,比较的是内存地址,而hashCode是根据内存地址得到哈希值,内存地址一样的话,得到的哈希值肯定是一样的。
(2)两个对象hashCode相等的情况下,equals不一定相等。这是为什么呢,首先我们来说一下哈希表,哈希表结合了直接寻址和链式寻址两个方式,简单来说就是先计算出要插入的数据的哈希值,然后插入到相应的分组当中去,因为哈希函数返回的是一个int类型,所以最多也就只有2的32次方个分组,对象多了,总有分组不够用的时候,这个时候,不同的对象就会产生相同的哈希值,也就是哈希冲突现象,此时就可以通过链地址法把分组用链表来代替,同一个链表上的对象hashCode肯定是相等的,因为是不同的对象,所以内存地址不同,所以他们的equals肯定是不相等的。这的hashCode就相当于是人名,equals就相当于身份证号
3 重写equals和hashCode
3.1 都不重写
import java.util.*; public class Test { public static void main(String[] args) { Person p1 = new Person(); p1.name = "张三"; Person p2 = new Person(); p2.name = "李四"; Person p3 = new Person(); p3.name = "张三"; Set set = new HashSet(); set.add(p1); set.add(p2); set.add(p3); System.out.println(p1.equals(p2)); // false System.out.println(p1.equals(p3)); // false } }
// 不重写的情况下默认根据内存地址生成的哈希值来进行比较,内存地址不同就生成了不同的哈希值(不考虑哈希冲突),所以就插入了重复的数据。
3.2 只重写equals
import java.util.*; public class Test { public static void main(String[] args) { Person p1 = new Person(); p1.name = "张三"; Person p2 = new Person(); p2.name = "李四"; Person p3 = new Person(); p3.name = "张三"; Set set = new HashSet(); set.add(p1); set.add(p2); set.add(p3); System.out.println(p1.equals(p2)); // false System.out.println(p1.equals(p3)); // true } } class Person { String name; //覆盖 equals public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Person) { Person p = (Person)obj; return this.name.equals(p.name); } return false; } }
3.3 只重写hashCode
import java.util.*; public class Test { public static void main(String[] args) { Person p1 = new Person(); p1.name = "张三"; Person p2 = new Person(); p2.name = "李四"; Person p3 = new Person(); p3.name = "张三"; Set set = new HashSet(); set.add(p1); set.add(p2); set.add(p3); System.out.println(p1.equals(p2)); // false System.out.println(p1.equals(p3)); // false } } class Person { String name; public int hashCode() { return (name==null) ? 0:name.hashCode(); } }
3.4 重写equals和hashCode
import java.util.*; public class Test { public static void main(String[] args) { Person p1 = new Person(); p1.name = "张三"; Person p2 = new Person(); p2.name = "李四"; Person p3 = new Person(); p3.name = "张三"; Set set = new HashSet(); set.add(p1); set.add(p2); set.add(p3); System.out.println(p1.equals(p2)); // false System.out.println(p1.equals(p3)); // true } } class Person { String name; public int hashCode() { return (name==null) ? 0:name.hashCode(); } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Person) { Person p = (Person)obj; return this.name.equals(p.name); } return false; } }
4. 总结
如果equals方法和hashCode方法同时被重写,则需满足hashCode 的常规协定:
(1)在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
(2)如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
(3)如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
所以,在重写方法的时候,有以下结论:
两个对象equals相等,则它们的hashcode必须相等,反之则不一定。
(4)重写equals一定要重写hashCode吗
答案是不一定的,如果你仅仅是为了比较两个对象是否相等只重写equals就可以,但是如果你使用了hashSet、hashMap等容器,为了避免加入重复元素,就一定要同时重写两个方法。