一个粗心的问题引发的思考
问题的起因是编写的以下代码今天出现了问题:
private List<Integer> ids = new ArrayList<Integer>(); //new a list to store user id ...... ids.add(user.getID()); //add user id to ids ...... ids.remove(user.getID()); //remove user id from ids
细心的朋友很容易就找到问题所在。因为List提供两个remove方法:
remove(int index) remove(Object o)
而我使用Integer作为泛型,这样调用remove方法时,编译器自动将Integer转化为了Int,所以实际调用的是remove(int index)这个方法。显然,我的本意是调用remove(Object o)。这样的问题,如果不是程序抛出IndexOutBoudsException,对于一向粗心的我还一直蒙在鼓里。
本文的重点在于解决此问题的过程中引发的一系列思考,并结合最近再次拜读《Effective Java》这本经典著作的结果给出了一些对java语言的心得体会。
回到问题本身来,最直接的解决方案如下:
ids.remove(Integer.valueOf(user.getID()));
经过测试,该方案是可行的。
心血来潮,我提出了以下新的方案,它是否可行呢:
ids.remove(new Integer(user.getID()));
初步分析,应该是不行的,因为传入的参数是一个新的Integer对象,而与原来的Integer是不相等的,所以remove操作将失败。
遗憾的是,以上分析是错误的,事实证明第二种方案也是可行的。为什么呢?
回想起《Effective Java》一书中,介绍"equals"方法的一段,有了启发。以上分析的结论是基于remove方法是以对象引用是否相等来判断,即通过“==”操作符来判断是否存在指定移除的对象。然而,它有没有可能是通过Integer对象的"equals"方法来判断的呢?如果是,两个不同的Integer对象,他们的值相同时,"equals"方法是否返回true呢?
验证第二个疑问很简单,写段程序验证一下就可。事实上,两个不同的Integer对象,他们的值相同时,"equals"方法是返回true的。因为Integer类override了Object.equals方法
验证第一个疑问也很简单,查看JDK源码:)
这时候,我突然想起了大学英语课文中关于爱因斯坦研究玩具鸟原理的文章(MS内容是这样的吧,大意是爱因斯坦想知道一只玩具鸟是怎么发出叫声的,而他一直不愿意拆开玩具来知道答案,直到最后他经过冥思苦想来得到答案时也没有拆开玩具鸟)当然,老爱是伟大的理论物理学家,咱只是个小Coder,没法比,只是好奇罢了。
扯远了,回到正题。可以通过以下测试来验证remove是否通过equals方法来判断的
private List<INT> INTs = new ArrayList<INT>(); public void testListINT() { System.out.println("-----testListINT-----"); INTs.add(new INT(1)); INTs.add(new INT(2)); INTs.remove(new INT(1)); INTs.remove(new INT(2)); System.out.println("size="+INTs.size()); System.out.println("=====END====="); } class INT { public int i; public INT(int i) { this.i = i; } }
输出结果为:
-----testListINT-----
size=2
=====END=====
Object的equals方法,对于不同的对象返回的是false。在INT中override equals方法:
private List<INT> INTs = new ArrayList<INT>(); public void testListINT() { System.out.println("-----testListINT-----"); INTs.add(new INT(1)); INTs.add(new INT(2)); INTs.remove(new INT(1)); INTs.remove(new INT(2)); System.out.println("size="+INTs.size()); System.out.println("=====END====="); } class INT { public int i; public INT(int i) { this.i = i; } public boolean equals(Object arg0) { if(arg0 instanceof INT){ if(((INT)arg0).i == i) return true; } return false; } }
输出结果为:
-----testListINT-----
size=0
=====END=====
所以说,remove方法还是通过equals方法来判断指定的对象是否与列表中的对象相同。
《Effective Java》一书中还提到:
“在每个改写了equals方法的类中,你必须也要改写hashCode方法。如果不这样的话,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于hash的集合类结合在一起正常运作”
“相等的对象必须具有相等的hash code”
为了验证,再添加以下代码:
public void testMapINT() { System.out.println("-----testMapINT-----"); INTmap.put(new INT(1), "1"); INTmap.put(new INT(2), "2"); INTmap.remove(new INT(1)); INTmap.remove(new INT(2)); System.out.println("size="+INTmap.size()); System.out.println("=====END====="); }
输出为:
-----testMapINT-----
size=2
=====END=====
而在INT中override hashCode方法之后:
class INT { public int i; public INT(int i) { this.i = i; } public boolean equals(Object arg0) { if(arg0 instanceof INT){ if(((INT)arg0).i == i) return true; } return false; } public int hashCode() { return i; } }
输出为:
-----testMapINT-----
size=0
=====END=====
总结本文,List中,通过equals方法来判断元素是否相同;Hash类型的Collection子类,如:HashMap等是通过hashCode的返回值来标示Key值。