发现实现Collection借口中addAll()方法存在一个微妙的陷阱
今天我在自己构建JDK中的ArrayList时发现了一个有趣的陷阱。现在把它陈述出来:
对于 boolean Collection<E>.addAll(Collection<? extends E> c)方法,源码里面
有这样的说明:
<------------------------------------------------------------------->
/**
* Adds all of the elements in the specified collection to this set if
* they're not already present
(optional operation).
If the specified
* collection is also a set, the <tt>addAll</tt> operation effectively
* modifies this set so that its value is the <i>union</i> of the two
* sets.
The behavior of this operation is unspecified if the specified
* collection is modified while the operation is in progress.
*
*........
*........
<------------------------------------------------------------------->
对于第一句话有个补充说明(optional operation:非限制的操作),这就意味着有这
样一种情况可能发生:
假如有一个类引用了Collection借口(就拿JDK中的ArrayList类来说吧),并没
有限制不能向里面添加已经存在了的元素。即:
以下操作合法:
Collection c = new ArrayList();
c.add("a");
c.add("b");
c.addAll(c);
以上的代码是合法的。而且结果是有两个a和两个b。
今天我自己在编写一个MyArrayList类时也引用Collection等借口。有意思的是在实
现addAll()方法时遇到了问题。
这个问题原来是个很微妙的陷阱。现在我把它列出来。
我自己写实现的boolean addAll(Collection<? extends E> c)方法如下,是有个
bug的。后来才发现(太隐秘了,开始还没发现)。
<------------------------------------------------------------------->
public class MyArrayList extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private int size;
//......
public boolean addAll(Collection<? extends E> c) {
Object[] a = c. toArray();
//把c转化为数组
ensureCapacity( size + c.size() );
//分配新的空间
for(int i =0 ; i < elementData.length ; i++){
//逐个添加
add( a[ i ] );
}
}
//......
}
<------------------------------------------------------------------->
这断代码看起来没有问题,运行起来好像也没有什么问题,就连有重复数据的两个
数组也可以相连。
但是这断代码有个严重的错误:如下:
Collection c = new MyArrayList();
c.add("a");
c.add("b");
c.addAll(c);
这个时候就出错了,而且很严重,死循环了,不出来了。我想了很久才会过意来,
原来Collection源代码中那句注解的意义就在这里了。“ The behavior of this op-
eration is unspecified if the specified
collection is modified while the operation is in progress.”
之所以会死是因为被添加的那个c被改变了,c在不断的往自己里面添加自己,
当然也就无止境了。这个操作的行为在运行时的确是不确定的。
但是,我拿JDK的ArrayList也做同样的操作,通过了,而且结果也正是我想要
的两个a两个b。为什么它就可以实现呢?
它是怎么实现的呢?后来我看了ArrayList的源代码,原来关键在于它用到了一
个System.arraycopy()方法。
ArrayList的源代码这样写道:
<------------------------------------------------------------------->
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew);
// Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
//可见此处是关键
size += numNew;
return numNew != 0;
}
<------------------------------------------------------------------->
于是我查了一下System.arraycopy()方法的代码。
里面有这样一条comment。
<------------------------------------------------------------------->
* If the <code>src</code> and <code>dest</code> arguments refer to the
* same array object, then the copying is performed as if the
* components at positions <code>srcPos</code> through
* <code>srcPos+length-1</code> were first copied to a temporary
* array with <code>length</code> components and then the contents of
* the temporary array were copied into positions
* <code>destPos</code> through <code>destPos+length-1</code> of the
* destination array.
<------------------------------------------------------------------->
通过这断解释可以看到System.arraycopy()的思想,其实也是可以想到的,就是把
“自己加自己”也变成“自己加别人”,最简单的方法就是找个临时变量。就是如果加
如的是自己,则先把自己Copy到一个临时的数组里面,然后再对这个临时文件操作即可。
今天又发现并解决了这个问题,算是有点收获,并且JDK中的ArrayList自己也模拟的
差不多了。
<------------------------------------------------------------------->
<------------------------------------------------------------------->
对于第一句话有个补充说明(optional operation:非限制的操作),这就意味着有这
样一种情况可能发生:
有限制不能向里面添加已经存在了的元素。即:
以下操作合法:
以上的代码是合法的。而且结果是有两个a和两个b。
今天我自己在编写一个MyArrayList类时也引用Collection等借口。有意思的是在实
现addAll()方法时遇到了问题。
这个问题原来是个很微妙的陷阱。现在我把它列出来。
我自己写实现的boolean addAll(Collection<? extends E> c)方法如下,是有个
bug的。后来才发现(太隐秘了,开始还没发现)。
<------------------------------------------------------------------->
public class MyArrayList extends AbstractList<E>
<------------------------------------------------------------------->
这断代码看起来没有问题,运行起来好像也没有什么问题,就连有重复数据的两个
数组也可以相连。
但是这断代码有个严重的错误:如下:
这个时候就出错了,而且很严重,死循环了,不出来了。我想了很久才会过意来,
原来Collection源代码中那句注解的意义就在这里了。“ The behavior of this op-
eration is unspecified if the specified
collection is modified while the operation is in progress.”
<------------------------------------------------------------------->
<------------------------------------------------------------------->
于是我查了一下System.arraycopy()方法的代码。
里面有这样一条comment。
<------------------------------------------------------------------->
<------------------------------------------------------------------->
通过这断解释可以看到System.arraycopy()的思想,其实也是可以想到的,就是把
“自己加自己”也变成“自己加别人”,最简单的方法就是找个临时变量。就是如果加
如的是自己,则先把自己Copy到一个临时的数组里面,然后再对这个临时文件操作即可。
今天又发现并解决了这个问题,算是有点收获,并且JDK中的ArrayList自己也模拟的
差不多了。