Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有继承这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法。
比如:HashMap、HashTable、HashSet,所以涉及到使用hash值进行优化存储的地方,都会用到hashCode。
1.首先调用对象的hashCode()方法获得该对象的哈希码表。
2.然后根据哈希吗找到相应的存储区域。
3.最后取得该存储区域内的每个元素与该对象进行equals方法比较。
这样就不用遍历集合中的所有元素就可以得到结论,可见HashSet集合具有很好的对象检索性能。
hash与equals的关系:
hashCode就好比字典里每个字的索引,equals()好比比较的是字典里同一个字下的不同词语。
- 不管执行多少次获取hash值的操作,只要对象不变,那么hash值是固定的
- 若两个对象equals方法返回为true,则其hash值也应该是一样的。
- 若两个对象equals方法返回为false,则其hash值可能相同。
下面来看一下一个具体的例子:
RectObject对象:
package com.weijia.demo;
public class RectObject {
public int x;
public int y;
public RectObject(int x,int y){
this.x = x;
this.y = y;
}
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj){
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
final RectObject other = (RectObject)obj;
if(x != other.x){
return false;
}
if(y != other.y){
return false;
}
return true;
}
}
我们重写了父类Object中的hashCode和equals方法,看到hashCode和equals方法中,如果两个RectObject对象的x,y值相等的话他们的hashCode值是相等的,同时equals返回的是true;
下面是测试代码:
package com.weijia.demo;
import java.util.HashSet;
public class Demo {
public static void main(String[] args){
HashSet<RectObject> set = new HashSet<RectObject>();
RectObject r1 = new RectObject(3,3);
RectObject r2 = new RectObject(5,5);
RectObject r3 = new RectObject(3,3);
set.add(r1);
set.add(r2);
set.add(r3);
set.add(r1);
System.out.println("size:"+set.size());
}
}
-
我们向HashSet中存入到了四个对象,打印set集合的大小。
运行结果:size:2
因为重写了RectObject类的hashCode方法,只要RectObject对象的x,y属性值相等那么他的hashCode值也是相等的,先比较hashCode的值,r1和r2对象的x,y属性值不等,所以他们的hashCode不相同的,所以r2对象可以放进去,但是r3对象的x,y属性值和r1对象的属性值相同的,所以hashCode是相等的,这时候在比较r1和r3的equals方法,因为他么两的x,y值是相等的,所以r1,r3对象是相等的,所以r3不能放进去了,同样最后再添加一个r1也是没有没有添加进去的,所以set集合中只有一个r1和r2这两个对象 -
下面我们把RectObject对象中的hashCode方法注释,即不重写hashCode方法,再运行一下代码:
运行结果:size:3
这个结果也是很简单的,首先判断r1对象和r2对象的hashCode,因为Object中的hashCode方法返回的是对象本地内存地址的换算结果,不同的实例对象的hashCode是不相同的,同样因为r3和r1的hashCode也是不相等的,但是r1==r1的,所以最后set集合中只有r1,r2,r3这三个对象,所以大小是3 -
下面我们把RectObject对象中的equals方法中的内容注释,直接返回false,放開hashCode方法的注釋,运行一下代码:
运行结果:size:3
这个结果就有点意外了,我们来分析一下:
首先r1和r2的对象比较hashCode,不相等,所以r2放进set中,再来看一下r3,比较r1和r3的hashCode方法,是相等的,然后比较他们两的equals方法,因为equals方法始终返回false,所以r1和r3也是不相等的,r3和r2就不用说了,他们两的hashCode是不相等的,所以r3放进set中,再看r4,比较r1和r4发现hashCode是相等的,在比较equals方法,因为equals返回false,所以r1和r4不相等,同一r2和r4也是不相等的,r3和r4也是不相等的,所以r4可以放到set集合中,那么结果应该是size:4,但為什麼是3呢?
其實HashSet首先是判断hashCode是否相等,不相等的话,直接跳过,相等的话,然后再来比较这两个对象是否相等或者这两个对象的equals方法,因为是进行的或操作,所以只要有一个成立即可,那这里我们就可以解释了,其实上面的那个集合的大小是3,因为最后的一个r1没有放进去,以为r1==r1返回true的,所以没有放进去了。所以集合的大小是3。
最后我们在来看一下hashCode造成的内存泄露的问题:看一下代码:
package com.weijia.demo;
import java.util.HashSet;
public class Demo {
public static void main(String[] args){
HashSet<RectObject> set = new HashSet<RectObject>();
RectObject r1 = new RectObject(3,3);
RectObject r2 = new RectObject(5,5);
RectObject r3 = new RectObject(3,3);
set.add(r1);
set.add(r2);
set.add(r3);
r3.y = 7;
System.out.println("删除前的大小size:"+set.size());
set.remove(r3);
System.out.println("删除后的大小size:"+set.size());
}
}
运行结果:
删除前的大小size:3
删除后的大小size:3
调用了remove删除r3对象,以为删除了r3,但事实上并没有删除,這樣多次这样操作之后,内存就爆了。因為在调用remove方法的时候,会先使用对象的hashCode值去找到这个对象,然后进行删除,但我们在修改了r3对象的y属性的值,又因为RectObject对象的hashCode方法中有y值参与运算,所以r3对象的hashCode就发生改变了,所以remove方法中并没有找到r3了,所以删除失败。即r3的hashCode变了,但是他存储的位置没有更新,仍然在原来的位置上,所以当我们用他的新的hashCode去找肯定是找不到了。
要点:
Object中的hashCode方法返回的是对象本地内存地址的换算结果。
HashSet判断要放入的元素是否相等:
1.hashcode是否相等,不相等放入。
2.如果hashcode相等,判断equals方法和==方法,如果都不相等放入。equals和==方法只要有一个相等,HashSet认为是相同的元素。
网友评论