`
pandonix
  • 浏览: 399795 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

一个粗心的问题引发的思考

阅读更多

问题的起因是编写的以下代码今天出现了问题:

 

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值。

分享到:
评论
2 楼 ocaicai 2011-09-11  
真是一个勤于思考的孩子!
1 楼 beneo 2010-06-20  
看了好几遍,对API不熟悉的飘过。。

相关推荐

Global site tag (gtag.js) - Google Analytics