hashCode equal避免的几个误区

来源:互联网 发布:简述osi七层网络模型 编辑:程序博客网 时间:2024/06/06 03:28
对于hashcode方法和equals方法,我们需要注意以下几点:
1.equals方法所需要的几个特性
?什么情况下进行equals的比较?答案是用equals比较的只是值本身,对于equals方法,读者需要记住按照两种情况来运用,一比较基本类型的值的比较,二比较的是对象。equals比较的只是值或者对象的内容。
非空的x,y,z,
一致性:无论x.equals(y)比较多少次,都应该返回true
反射性:任何对象和它本身比较都应该返回true
类推性:x.equals(y), y.equals(z),x.equals(z)都应该返回true
对称性:x.equals(y)和y.equals(x)返回的应该都是true.
非空和null比较永久都该返回false.
2.hashcode和equals的关系
对于java中的object类来说,hashcode返回的是对象的内存地址(可以粗略的这样认为),String类(以及其他原生数据类型如int ,flat等的引用数据类型Integer Float等)重写了object的hashcode和equals方法,但是在这里注意,不要被基本数据类型的对于hashcode和equals方法重写所迷惑了。基本数据类型对于OBJECT的该2个方法的重写并不意味着你再现实的代码中也同样照做(下面会解释).基本类型String等对于这两个方法的重写其实着重在内容上。
Java定义的规则中硬性的绑定了2个原则:
hashcode值相同的2个对象(注意这里是对象)并不一定是同一个对象,也就是说equals方法返回的不一定是true,即对象的内容可能不同。
equals方法返回true,hashcode的返回的int类型的值并不一定相同,即内容相同的两个对象不一定是同一个对象(名字相同的人不一定是同一个人)。
相同对象必定equals方法返回true且hashcode值相同

3.重点关键所在:现实当中重写equals方法必定要重写hashcode?
读者可以直接读取java的目录中的rt.jar,找到string类的源码,可以发现,String的hashcode方法是读取字符串中的每个字符,并不是对象的内存地址,换句话来说,object的hashcode方法在string中被重写,equals方法也是对2个字符串内容进行比较,hashcode也被定义成与内容相关,所以我们可以发现为了保证基本类型对象是同一个对象:必须让hashcode返回值和equals方法都绑定到对于内容的处理上,换句话来说,满足了上面第二点的分析,同一个对象hashcode,equals2个方法都必须满足
现实当中,只有在用到集合类的时候,才需要重写hashcode方法,否则不需要重写hashcode方法,比较2个对象只需要equals方法即可,当用到集合类时,重写hashcode方法并不是效率的考虑,而是必须重写hashcode方法,否则会出现宏观上的理解错误:
mport java.util.*;
public class HashSetTest
{
public static void main(String[] args)
{
HashSet hs=new HashSet();
hs.add(new Student(1,"t1"));
hs.add(new Student(2,"t2"));
hs.add(new Student(3,"t3"));
hs.add(new Student(1,"t1"));

Iterator it=hs.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
}
class Student
{
int num;
String name;
Student(int num,String name)
{
this.num=num;
this.name=name;
}
public String toString()
{
return num+":"+name;
}
}
输出结果为:
1:t1
1:t1
3:t3
2:t2

可见这样的效果并不是我们从宏观上想要的,对于我们自己定义的对象student来说,我们需要num,name相同的对象就应当是同一个对象,但是由于没有重写equals方法和hashcode方法,使用new 运算符创建对象时,内存地址是不同的,因为沿用了object的hashcode和equals方法,导致set对传入的对象的判断上发生错误,导致我们重复塞了2个“相同的对象”(这种相同是指我们自己定义的规则,比如学号相同,名称相同我们就认为是同一个对象)。


读者可以查看HashSet的源码,可以发现HashSet底层采用HashMap实现,HashMap并不允许重复的K值存入,所以当存入新的K时,会对传入的参数K的hashCode值进行判断,如果K相同,那么覆盖老的V,用新的V:
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
我们可以发现这里双重调用到了hash和equals,所以为了避免存入重复对象,我们需要重写我们自己的student类的hashcode和equals方法,以告诉hashSet避免存入我们宏观意义上的重复对象。
而对于String等基本类型来说,比如
HashSet hs=new HashSet();
hs.put("1","2");
hs.put("1","3"); //第二步
当进行put时,代码第二步处会对传入的"1"(K)进行判断,因为已经存在一个K的hashcode以及equals方法和第二步处传入的"1"相同,所以视作同一个K处理。



最后总结一点:一般项目中如果需要用到集合类如hashtable,hashset时,那么我们自己新建的对象如果需要存入该集合类,那么必须重写hashcode以及equals方法,不能漏掉任何一个。