对hashset与hashmap中equals和hashcode的思考
偶然看了f543711700大大写的关于“对java如何判断HashSet和HashMap中相同元素的研究”一文(连接如下)
http://f543711700.iteye.com/blog/800929
对之中提出的“equals返回真,则hashcode值相等”一度抱有怀疑态度,因为之前在实际的编码工作中,确实覆盖了object的equals方法,但从未认真思考hashcode的值是否也相等。
?
为了搞清楚这个问题,首先在JDK源码中看看object是怎么定义equals方法的吧:
?
Note that it is generally necessary to override the <tt>hashCode</tt> method whenever this method is overridden, so as to maintain the general contract for the <tt>hashCode</tt> method, which states that equal objects must have equal hash codes. public boolean equals(Object obj) {return (this == obj); }?
?
?
看的出来在object中equals的具体实现就是==,这也是为什么要覆盖这个方法的原因之一,注释的那一段,说明若要覆盖equals方法,同时也要覆盖hashcode方法。力求做到相同的对象有着相同hashcode值。
?
在hashmap中调用put方法时,会进行校验,以确保散列表中没有相同的对象存在,源码如下:
public V put(K key, V value) {if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); 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))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }?在判断是否已经存在这样一个键时,源码里既判断了hashcode的值也使用了equals方法,并且在&&后面的判断中同样也是使用了这两者,只不过是或得关系了。
若是存在这样一个键,会将原来的value覆盖,这就是为什么f543711700文中第二个测试代码size会返回1,因为第一个k-v对已经被第二个k-v对覆盖了。
?
通过与其他同事的交流,只要没有涉及hash相关的代码倒是不用都覆盖hashcode方法,毕竟使用在这种情况下使用它的机会也很少,但是在涉及hash相关的地方一定要覆盖,确保它与equals同步。
?
拿hashset举一个例子吧:
PS:这个例子来源自bepatient大大,写的很恰当,我就借来了
原文链接:http://bepatient.iteye.com/blog/702819
HashSet是一个无序不可以重复储存的集合。HashSet是靠hashcode方法,如果对应的两个对象所返回的hashcode方法的值是相等的,则表明该两个对象“相等”。所以一般情况下,用户需要对要储存到HashSet的对象所在的类重写hashcode方法,而不是用继承自Object的hashcode方法,因为Object 类定义的 hashCode 方法会针对不同的对象返回不同的整数(这一般是通过将该对象的内部地址转换成一个整数来实现的),一般不符合用户的需求。
要想在HashSet中知道对象的位置,就要先计算该对象的hashcode,然后与散列表的列表的总数取余,所得结果就是保存这个元素的列表的索引。
当一个对象被储存进HashSet集合后,就不能再修改这个对象中的那些参与计算hashcode值的属性了,否则对象修改后的hashcode与最初储存到HashSet的值不一致,在这种情况下,即使在contains方法中使用该对象的当前引用作为参数区HashSet集合中检索对象,也将返回不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
为什么会杀不了呢:因为,当集合调用remove方法的时候,会从对象得到hashcode,但是原先对象储存的时候所对应的hashcode不是现在的hashcode,所以定位的不是相同的对象,因此删除不了。
所谓内存泄露是指该对象已经不需要再用,但是一直占着内存空间。
示例代码:
package mm.testJava;
import java.util.*;
public class Person {??? ?private String str;?private int age;??public Person(){???}??public Person(String str,int age){??this.str=str;??this.age=age;?}??public void setName(String str){??this.str=str;?}??public int hashCode(){??int code;????if(str=="p1") code = 1;??else if(str=="p2") code = 2;??else if(str=="p3") code = 3;??else code = 4;?????return code;?}}?
package mm.testJava;import java.util.Collection; import java.util.Date; import java.util.HashSet; public class HashSetTest { public static void main(String[] args) { Collection c = new HashSet(); Person p1 = new Person("p1",23); Person p2 = new Person("p2",23); Person p3 = new Person("p3",23); c.add(p1); c.add(p2); c.add(p3); p1.setName("p4"); c.remove(p1); System.out.println(c.size());//size的值依然是3 } }?
?