在JAVA中使用HashMap和HashSet时,往往会考虑到key类型的问题。如果我们使用的key类型为java封装的基本类型(Integer、Double、String等)时,当两个key的内容相同时,他们在HashMap和HashSet中也会被认为是相同的。比如下面代码:
String s1 = new String("test");
String s2 = new String("test");
HashMap<String,Integer> map = new HashMap<String,Integer>();
HashSet<String> set = new HashSet<String>();
map.put(s1,1);
map.put(s2,1);
set.add(s1);
set.add(s2);
System.out.println("HashMap的大小:" + map.size());
System.out.println("HashSet的大小:" + set.size());
输出结果为:
HashMap的大小:1
HashSet的大小:1
其中s1和s2为String类型,且内容都为“test”,而HashMap和HashSet在添加s1、s2时,把它们识别为相同的key值,因此HashMap和HashSet的大小都为1。
但是,当我们把一个Object类型的对象作为HashMap和HashSet的key时,就会出现内容相同的对象被识别为不同key值的情况。比如以下代码:
public class Test {
int a;
public Test(int a) {
super();
this.a = a;
}
public static void main(String args[]){
Test t1 = new Test(1);
Test t2 = new Test(1);
HashMap<Test,Integer> map = new HashMap<Test,Integer>();
HashSet<Test> set = new HashSet<Test>();
map.put(t1,1);
map.put(t2,1);
set.add(t1);
set.add(t2);
System.out.println("HashMap的大小:" + map.size());
System.out.println("HashSet的大小:" + set.size());
}
}
输出结果为:
HashMap的大小:2
HashSet的大小:2
可以看出,t1和t2虽然内容相同,有共同的成员a=1,但还是被HashMap和HashSet识别为不同的key值,这是为什么呢?
经过查看一些资料后,发现HashMap和HashSet中是通过key的hashCode来找key的位置,当我们创建一个对象时,其hashCode肯定是不同的,所以我们需要重写该对象的hashCode()方法,使得对象的hashCode跟其内容相关。比如以下代码:
public class Test {
int a;
public Test(int a) {
super();
this.a = a;
}
@Override
public int hashCode(){
return a;
}
public static void main(String args[]){
Test t1 = new Test(1);
Test t2 = new Test(1);
HashMap<Test,Integer> map = new HashMap<Test,Integer>();
HashSet<Test> set = new HashSet<Test>();
map.put(t1,1);
map.put(t2,1);
set.add(t1);
set.add(t2);
System.out.println("HashMap的大小:" + map.size());
System.out.println("HashSet的大小:" + set.size());
}
}
输出结果仍然为:
HashMap的大小:2
HashSet的大小:2
我们将Test对象的hashCode改为其成员变量a的值,这样t1和t2的内容以及hashCode都相同了,但仍然无法被HashMap和HashSet识别为相同的key值。
又进行一阵摸索后,发现除了hashCode()方法外,equals()方法也有很大的作用,Integer、Double、String等都重写了Object的hashCode()和equals()方法,所以才能被HashMap和HashSet根据内容来识别。因此,我们来重写Test的equals()方法。
Java语言对equals()方法的要求如下,这些要求是必须遵循的:
• 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
• 反射性:x.equals(x)必须返回是“true”。
• 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
• 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
• 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
比如以下代码:
public class Test {
int a;
public Test(int a) {
super();
this.a = a;
}
@Override
public int hashCode(){
return a;
}
@Override
public boolean equals(Object o){
if(this.a == ((Test)o).a)
return true;
else
return false;
}
public static void main(String args[]){
Test t1 = new Test(1);
Test t2 = new Test(1);
HashMap<Test,Integer> map = new HashMap<Test,Integer>();
HashSet<Test> set = new HashSet<Test>();
map.put(t1,1);
map.put(t2,1);
set.add(t1);
set.add(t2);
System.out.println("HashMap的大小:" + map.size());
System.out.println("HashSet的大小:" + set.size());
}
}
输出得到了我们满意的结果:
HashMap的大小:1
HashSet的大小:1
这里,我们重写的equals()方法,只是简单地根据Test对象的成员变量a的值来比较大小。这样,HashMap和HashSet都把t1和t2识别为相同的key值。
以后当我们想往HashMap和HashSet中添加内容相同但不是指向同一内存地址的对象,但又不想有重复的对象时,就可以重写hashCode()和equals()方法解决。
最后注明一点:
equals()相等的两个对象,hashcode()一定相等;
equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。