欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-01-12 18:44:10
...

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

 

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

相关标签: JDK