偶然看了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
}
}