发现实现Collection借口中addAll()方法存在一个微妙的陷阱

发现实现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自己也模拟的
差不多了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值