Java ArrayList subList(..)的坑

Java ArrayList subList引发的故障

业务场景:

有n个数的List,需要对前n-1个数进行排序然后把最后的数放入到里面 输出一个新的List;
代码实现如下(非原业务代码):

        List<Integer> list = Lists.newArrayList();
        //初始化数组
        for (int i = 1; i < 11; i++) {
            list.add(i);
        }
        for (int i = 0; i < 10; i++) {
            List<Integer> listNew  = new ArrayList<>();
                listNew = list.subList(0, list.size() - 1);
                listNew.sort(new Ordering<Integer>() {
                                 @Override
                                 public int compare(Integer integer, Integer t1) {
                                     return integer - t1;
                                 }
                             }

                );
                listNew.add(list.get(list.size() - 1));
            System.out.println("after loop " + i + " listNew size : " + listNew.size());
            System.out.println("after loop " + i + " handle list size :  " + list.size());

        }
导致问题

每次代码迭代 sublist后在add 会导致原数组list的size不断地变大,改变了原数组的大小,对后面使用list的地方造成了不可预知的影响。

问题原因

通过查看ArrayList的源码发现在调用sublist函数时并非返回一个新的List 返回了一个SubList的对象具体代码如下:

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

SubList为ArrayList的一个内部类,分析Sublist部分源码如下:

private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

        SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }
···
}

通过构造方法可以看出 subList存在了一个对父类ArrayList的引用即对原始的ArrayList的引用,并且存了相对于原数组的fromIndex 和size。接下来重点看下sublist 相关的add,set,remove方法。

当我们对一个SubList对象调用add 方法时 实际上调用的是 AbstractList的add方法,源码如下:

public boolean add(E e) {
        add(size(), e);
        return true;
    }

然后调用 SubList add方法,源码如下:

 public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

通过sublist的源码可以明确的看到每次对一个SubList进行add操作是,底层是在父类的ArrayList中添加的了一个元素,并把自己的size+1 。注意对于父类的ArrayList并不是在最后面加入这个元素是在子类的toIndex后面加入这个元素,保证连续性。

SubList set方法源码如下:

public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

SubList remove方法源码如下

public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

通过以上源码可以看到 对于SubList 进行元素操作需要谨慎,除非你能明确的知道进行操作后的影响否则不建议使用调用subList方法产生的List。可以使用以下方法避免:

List<Integer> listNew  = new ArrayList<>();
listNew.addAll(list.subList(0, list.size() - 1));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值