问题:当一个map在遍历时想找到其中的Key时,使用containKey(key)时,却发现返回值为false:
这个会困扰一个人很长时间,所以为节省小伙伴们的时间,来看看原因吧,帮助纠结的你回答上面的问题
我们忽略了某些东西,倘若我们想用Map存储自定义类会发生什么?
1)自定义类作为Map的Value:
1 import java.util.HashMap; 2 import java.util.Map; 3 4 class MyClass { 5 private static int counter = 0; 6 private int id; 7 8 public MyClass() { 9 id = ++counter; 10 } 11 12 @Override 13 public String toString() { 14 return "自定义类" + id; 15 } 16 } 17 18 public class TestMap { 19 public static void main(String[] args) { 20 Map<String, MyClass> testValue = new HashMap<String, MyClass>(); 21 MyClass mc1 = new MyClass(); 22 MyClass mc2 = new MyClass(); 23 testValue.put("NO.1", mc1); 24 testValue.put("NO.2", mc2); 25 System.out.println(testValue); 26 } 27 }/* 28 {NO.1=自定义类1, NO.2=自定义类2} 29 *//:~
我声明了一个MyClass,它给每一个对象会自动分配一个id,并且重载了toString()。在Map中key我用了String,value我用了我们自定义的类,大家会发现毫无问题,轻轻松松就打印出了我们期望得到的结果。难道事情就是这样简单?那么我们再来将自定义类作为key来试试
2)自定义类作为Map的Key(MyClass不变):
1 public class TestMap { 2 public static void main(String[] args) { 3 Map<MyClass, String> testKey = new HashMap<MyClass, String>(); 4 MyClass mc1 = new MyClass(); 5 MyClass mc2 = new MyClass(); 6 testKey.put(mc1, "NO.1"); 7 testKey.put(mc2, "NO.2"); 8 System.out.println(testKey); 9 10 // equal() 11 mc1 = new MyClass(); 12 mc1.id = 1; 13 testKey.put(mc1, "NO.3"); 14 System.out.println(testKey); 15 System.out.println("***********"); 16 17 // hashcode() 18 mc1 = new MyClass(); 19 mc1.id = 1; 20 System.out.println(mc1); 21 System.out.println(testKey); 22 System.out.println(testKey.containsKey(mc1)); 23 } 24 }/* 25 {自定义类1=NO.1, 自定义类2=NO.2} 26 {自定义类1=NO.1, 自定义类2=NO.2, 自定义类1=NO.3} 27 *********** 28 自定义类1 29 {自定义类1=NO.1, 自定义类2=NO.2, 自定义类1=NO.3} 30 false 31 *///:~
不知看完代码以及其输出结果后,是否发现了一些问题。
1.我们在前面讨论过Map在key上具有Set的性质,即Map中Key值是不能重复但是在代码26行我们会惊奇的发现运行结果竟然出现了两个相同的key但是有不同的value;
2.在代码18行我将mc1重新new了下,但是我将它的id又设为了1,mc1还是自定义类1,那么testKey中应该有的(而且还有两个!-_-),但是containKey返回的却是false。
要解答这些问题就要引出一个很重要的概念散列码:
1)什么是散列码?
不必用冗长的线性搜索技术来查找一个键,而是用一个特殊的值,名为“散列码”。散列码可以获取对象中的信息,然后将其转换成那个对象“相对唯一”的整数(int)。这是我从百度百科copy来的一段定义。
相信大家对散列表(或说哈希表)都有了解,我也将维基百科的链接放了上来大家可以去看下。其中有一个散列函数(即哈希函数),而散列函数产生的值就是散列码。
2)为什么需要散列码
大家都知道散列表,散列表能够是我们以O(1)的代价进行查询,那么散列函数的作用就是使每一个对象产生一个散列码,这个散列码将用来映射到内存地址空间,这样在进行查询时就能使得复杂度达到O(1),这其实就是哈希表的知识。
hashCode()和equals()
观察Java的源代码我们很容易发现Object基类中有两个函数hashCode()和equals()。hashCode()就像是哈希函数,用来产生散列码,而equals()函数Map会用来判断当前的键是否与表中存在的键相同。
这就解释了我们上面两个问题,由于Java中所有的类都继承自Object基类,上面两个类也不例外,而在Object中默认是将对象的地址作为散列码的,这就导致了我们重新new了mc1后,虽然新的mc1所有数据与原mc1一模一样,但是两者的地址不同,所以我们在containKey中找不到mc1的原因了;而同样的equals()函数也继承自Object,默认的equals()函数比较的是两个对象的地址,而代码中虽然前后put到Map中的mc1是完全相同的,但是两者地址不同,这就造成了同样的MyClass对象mc1在Map中出现了两次。
在写equals函数时必须满足下列5个条件:
- 自反性。对任意x,x.equals(x)一定返回true
- 对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。
- 传递性。对任意x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。
- 一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致。
- 对任何不是null的x,x.equals(null)一定返回false。
下面我们对上面的代码进行修改,让它达到我们想要的效果:
1 import java.util.HashMap; 2 import java.util.Map; 3 4 class MyClass { 5 private static int counter = 0; 6 public int id; 7 8 public MyClass() { 9 id = ++counter; 10 } 11 12 @Override 13 public String toString() { 14 return "自定义类" + id; 15 } 16 17 @Override 18 public boolean equals(Object obj) { 19 return this.id == ((MyClass) obj).id; 20 } 21 22 @Override 23 public int hashCode() { 24 return id; 25 } 26 } 27 28 public class TestMap { 29 public static void main(String[] args) { 30 Map<MyClass, String> testKey = new HashMap<MyClass, String>(); 31 MyClass mc1 = new MyClass(); 32 MyClass mc2 = new MyClass(); 33 testKey.put(mc1, "NO.1"); 34 testKey.put(mc2, "NO.2"); 35 System.out.println(testKey); 36 37 // equal() 38 mc1 = new MyClass(); 39 mc1.id = 1; 40 testKey.put(mc1, "NO.3"); 41 System.out.println(testKey); 42 System.out.println("***********"); 43 44 // hashcode() 45 mc1 = new MyClass(); 46 mc1.id = 1; 47 System.out.println(mc1); 48 System.out.println(testKey); 49 System.out.println(testKey.containsKey(mc1)); 50 } 51 }/* 52 {自定义类1=NO.1, 自定义类2=NO.2} 53 {自定义类1=NO.3, 自定义类2=NO.2} 54 *********** 55 自定义类1 56 {自定义类1=NO.3, 自定义类2=NO.2} 57 true 58 *///:~
我在MyClass中重载了hashCode()和equals()方法,为了简单我就直接使用了id作为比较和散列码,这样结果就达到了我们预期的效果。
其实散列函数的选择还是比较复杂的,因为散列表中我们要尽可能少的造成冲突,这里我们就是针对特定情况采取了特殊的散列函数选取。从我平时编程的角度而言,将自定义类作为key值的情况还是比较少的,如果真的需要用到,在选取散列函数方面一定要考虑周全,要在简单和性能速度方面做出权衡。
转自:http://www.cnblogs.com/xltcjylove/p/3852404.html