hashCode()方法和equals方法的重要性。
在Object中有两个重要的方法:hashCode()和equals(Object obj)方法,并且当你按ctrl+alt+s时会有Generator hashCode()和equals()。我们不禁会想这两个方法到底有什么用,让eclipse提供自动生成这两个方法的模板呢?
这两个方法主要是在hash的数据结构中。如HashSet<E> 、 HashMap<K,V>中。
内容提要:
下面的代码ElementWithoutHashAndEqual类中定义了字段、构造方法、把hashCode()和equals()方法注释掉。然后再HashTest这个类中进行测试,看看运行结果。然后你再把ElementWithoutHashAndEqual类中的hashCode()和equals()方法的注释去掉,你
再看看运行结果是什么。这时你就会发现hashCode()和equals()方法的重要性了。 接着再从源码的角度看看为什么需要些这两个方法.
//ElementWithoutHashAndEqual类的代码如下:
1 package com.qls.hashAndEquals; 2 3 public class ElementWithoutHashAndEqual { 4 private int age; 5 private String name; 6 7 public ElementWithoutHashAndEqual(int age, String name) { 8 this.age = age; 9 this.name = name; 10 } 11 12 /*@Override 13 public int hashCode() { 14 final int prime = 31; 15 int result = 1; 16 result = prime * result + age; 17 result = prime * result + ((name == null) ? 0 : name.hashCode()); 18 return result; 19 } 20 21 @Override 22 public boolean equals(Object obj) { 23 if (this == obj) 24 return true; 25 if (obj == null) 26 return false; 27 if (getClass() != obj.getClass()) 28 return false; 29 ElementWithoutHashAndEqual other = (ElementWithoutHashAndEqual) obj; 30 if (age != other.age) 31 return false; 32 if (name == null) { 33 if (other.name != null) 34 return false; 35 } else if (!name.equals(other.name)) 36 return false; 37 return true; 38 }*/ 39 40 public String toString() { 41 return "ElementWithoutHashAndEqual [age=" + age + ", name=" + name + "]"; 42 } 43 public static void main(String[] args) { 44 // TODO Auto-generated method stub 45 46 } 47 48 }
//HashTest的代码如下:
1 package com.qls.hashAndEquals; 2 3 import java.lang.reflect.InvocationTargetException; 4 import java.util.HashMap; 5 import java.util.HashSet; 6 7 /**在HashSet<E>,HashMap<K,V>中的元素E和K,必须要重写hashCode()和equals方法。 8 * 在大多数情况下E 写成String,Integer 9 * 等java自带的类。其实这些类已经重写了Object类中hashCode()和equals方法。 10 * 所以你感觉不到hashCode()方法和equals()方法的重要性。 11 * 这里我们自定义一个类,看它不重写hashCode()和equals方法会有什么后果。 12 * @author 秦林森 13 * 14 */ 15 public class HashTest { 16 17 public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { 18 // TODO Auto-generated method stub 19 /** 20 * 用HashSet做例子看HashSet<E>中的元素E类中不重写hashCode()方法,和equals(Object obj);方法 21 * 的效果: 22 * 下面HashSet的遍历结果为: 23 * ElementWithoutHashAndEqual [age=18, name=sixi] 24 ElementWithoutHashAndEqual [age=18, name=sixi] 25 ElementWithoutHashAndEqual [age=18, name=sixi] 26 咋看以下是不是感觉这违背了HashSet不能加重复元素的基本原则。 27 其实这并没有违背因为Object类中的equals方法是比较两个对象的地址的。 28 那么hashCode()为什么要重写呢? 29 再看一看HashMap做例子的情况吧! 30 */ 31 HashSet<ElementWithoutHashAndEqual> hashSet = new HashSet<ElementWithoutHashAndEqual>(); 32 hashSet.add(new ElementWithoutHashAndEqual(18, "sixi")); 33 hashSet.add(new ElementWithoutHashAndEqual(18, "sixi")); 34 hashSet.add(new ElementWithoutHashAndEqual(18, "sixi")); 35 for(ElementWithoutHashAndEqual e2:hashSet){ 36 System.out.println(e2); 37 } 38 /** 39 * HashMap做例子看看HashMap<K,V>中的元素K这个类不重写hashCode()和equals方法的严重性。 40 */ 41 HashMap<ElementWithoutHashAndEqual, Integer> hashMap=new HashMap<>(); 42 //先用反射向HashMap中添加元素: 43 hashMap.put(ElementWithoutHashAndEqual.class 44 .getConstructor(new Class[]{int.class,String.class}) 45 .newInstance(new Object[]{29,"ouyangfeng"}) 46 , 1); 47 //我们再看看用get(K key)方法能不能把value取出来。 48 Integer value = hashMap.get(ElementWithoutHashAndEqual.class 49 .getConstructor(new Class[]{int.class,String.class}) 50 .newInstance(new Object[]{29,"ouyangfeng"})); 51 System.out.println("value="+value);//输出结果为value=null,而不是value=1. 52 /** 53 * 直接new一个对象加在HashMap中。这时你就会看得非常明白了,我之所以用反射创建对象, 54 * 因为这种创建对象方式比较不容易看懂,从而迷惑你以为put(key,value) 和get(key)中的key是 55 * 同一个对象。 56 */ 57 hashMap.put(new ElementWithoutHashAndEqual(6, "AbrahamLincoln"), 3); 58 //下面语句的输出结果还为value=null. 59 System.out.println("value="+hashMap.get(new ElementWithoutHashAndEqual(6, "AbrahamLincoln"))); 60 /** 61 * 但是当你把ElementWithoutHashAndEqual这个类中的hashCode()、equals()方法的注释取消时, 62 * 输出结果就会变为你所想要的结果了,结果如下: 63 * ElementWithoutHashAndEqual [age=18, name=sixi] 64 value=1 65 value=3 66 生成hasCode和equals方法如下: 67 ctr+alt+s---->Generate hashCode() and equals()。便可以生成这两个函数了。 68 下面将出源码中看为什么需要在HashSet<E> 和HashMap<K,V>中的E、 K需要这两个函数。 69 */ 70 } 71 72 }
分析为什么需要hashCode()和equals()方法。分析如下:
首先讲在HashMap中如何根据key,得到value的。
第一步:int hash=hash(key)【这个函数调用了key类中的hashCode()函数】得到某个值,
第二步:根据函数int i=indexFor(hash,table.length);找到这个i值在数组table的值table[i],这个table[i]中的值就是Map.Entry<K,V>【其中Entry<K,V> 为Map中的一个静态类】
第三步:有可能多个对象产生的hashCode()值一样,所以在源码中进行了这样的遍历:
for (Entry<K,V> e = table[i]; e != null; e = e.next) {//这个遍历意思就是table[i]中所存放的值Entry<K,V>可能不止一个。用到了链表的数据结构,对各个节点进行遍历。
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))//对Entry<K,V>进行筛选,找到符合的Entry<K,V>,这个必然只有一个。
第四步:用Entry<K,V>中的getValue(),便可以取得value值了。上述步骤所用到的源码如下所示:
1 final Entry<K,V> getEntry(Object key) { 2 if (size == 0) { 3 return null; 4 } 5 6 int hash = (key == null) ? 0 : hash(key); 7 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 8 e != null; 9 e = e.next) { 10 Object k; 11 if (e.hash == hash && 12 ((k = e.key) == key || (key != null && key.equals(k)))) 13 return e; 14 } 15 return null; 16 }
1 public V get(Object key) { 2 if (key == null) 3 return getForNullKey(); 4 Entry<K,V> entry = getEntry(key); 5 6 return null == entry ? null : entry.getValue(); 7 }
在HashSet 中其实它添加元素也用到了Map中的方法,源码如下所示:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}。