深入理解equals()和hashCode()
问题1 怎么判断两个对象是相同的?
1.使用等号== 判断两个对象是否相同,这种是严格的相同,即内存中的同一个对象
Object的equal方法就是使用==判断两个对象是否相同
public boolean equals(Object obj) { return (this == obj); }
2.对于一些业务场景,当两个对象相同时,并不要求它们是内存中的同一个对象 ,只要满足业务上相同规则就可以认为它们相同。比如对于学生对象student1,student2 ,业务上可以定义为只要studentId相同,它们就是同一个对象,或者业务上可以可以定义为只要它们的所有属性值相同也可以认为它们相同。
问题2 集合set要求元素是唯一的,怎么实现?
要实现元素的唯一,需要在往集合set中添加元素时,判断集合set是否存在相同的元素,如果存在,则不添加,反之。
那么怎么确定两个元素是否相同,这里就涉及到问题1的讨论。
1.如果是使用等号==判断两个元素是否相同,即默认使用Object的equals的方法。
2.如果没有使用等号==判断两个元素是否相同,而是按照某种业务规则判断两个元素是否相同,即重写了Object的equals的方法。
问题3 HashSet怎么确保元素的唯一?
HashSet是用使用HashMap来实现的,元素作为HashMap的key添加到HashMap中,由于HashMap的key要求唯一,所以就实现了HashSet的元素唯一。
HashSet的部分代码如下
private transient HashMap<E,Object> map; //依赖HashMap ... public boolean add(E e) { return map.put(e, PRESENT)==null; }
private transient HashMap<E,Object> map;
//HashSet的add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashMap的部分代码如下
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //先比较hash值,如果hashCode不同,则对象不同,如果hashCode相同,再看继续比较。。。。 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
问题4 hashCode的意义何在?
先了解HashMap的实现原理,HashMap实际上是数组和链表的组合体,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当我们往HashMap中put元素的时候(这里假设不是重复元素),先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。综上所述,hashCode是用于计算元素在数组中的位置的。
hashCode还有一个作用,就是判断是否是重复元素,将在问题5中描述。
问题5 在问题3中为什么要先比较hashCode?
先看下面代码
Student student=new Student();
HashSet<Student> hashSet=new HashSet<Student>();
hashSet.add(student);
hashSet.add(student);
hashSet.add(student);
我们来看怎么比较是否是相同呢
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
e.hash == hash 会是true
(k = e.key) == key 会是true
所以是同一个对象,student只会被添加一次。
Student student1=new Student();
student1.setId(1);
student1.setName("lion");
.....
Student student2=new Student();
student2.setId(1);
student2.setName("lion");
.....
student1.setName("jack");
HashSet<Student> hashSet=new HashSet<Student>();
hashSet.add(student1);
hashSet.add(student2);
我们来看怎么比较是否是相同呢
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
e.hash==hash 是true还是false 依赖 Student的hashCode()方法
(k = e.key) == key 会是false
key.equals(k) 是true还是false 依赖 Student的equals()方法
这里的student1和student2所有属性值是一样的,从业务上student1和student2是同一个对象,我们必须重写Object的equals方法(使用等号==判断对象是否相同),否则student1和student2是不同对象,会重复添加到HashSet中,违背了Set元素唯一的要求。
我们重写的equals方法可能逻辑很复杂,性能上非常耗时,所以我们先比较hashCode ,通过hashCode来区分是否是不同的对象,但是我们必须对hashCode定义约束,即hashCode不同时,对象肯定不同(equals 方法返回false),并且在 Java 应用程序执行期间,对象没有被修改过的情况下在对同一对象多次调用 hashCode
方法时必须一致地返回相同的整数。
Object中对hashCode方法定义
public native int hashCode();
它是一个native方法,是对对象内存地址的一个映射值,内存中不同的对象,该方法返回的值肯定不同。
JDK对hashCode 方法的描述,主要是提升性能,当然也用于计算元素在数组中的位置的。
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by
HashMap
.
对hashCode方法的约束
The general contract of hashCode is:
-
- 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.
- 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.
- It is not required that if two objects are unequal according to the 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.
两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode
方法不 要求一定生成不同的整数结果。hash会出现冲突,不同对象它们的hashCode相同时,就会出现hash冲突。我们希望元素尽可能分布均衡,尽可能做到不同对象其hashCode不同,就可以减小hash冲突,提高哈希表的性能。
综上,先比较hashCode 主要是性能上的考虑。
问题6 当重写equals方法,必须重写hashCode方法吗?
不是必须的,得看具体的情况
- 当equals方法返回的结果和使用等号比较的结果是一致的时候,是没有必要重写hashCode方法。
当用等号比较对象,只有是内存中同一个对象实例,才会返回true,当然调用其hashCode()方法肯定返回相同的值,这满足了满足了hashCode的约束条件,所以不用重写hashCode()方法。
- 当equals方法返回的结果和使用等号比较的结果是不一致的时候,就需要重写hashCode方法。
当重写后的equals方法不认为只有是在内存中同一个对象实例,才返回true,如果不重新hashCode方法()(Object的hashCode()方法 是对内存地址的映射),hashCode方法返回的值肯定是不同的,这违背了hashCode的约束条件,所以必须要重新hashCode方法,并满足对hashCode的约束条件。