如何优雅的删除List中的元素

问题

如何删除list中index为偶数的元素?简单分析:首先我们得明确一点,就是不能边for循环,边删除元素,会出现越界的异常。那我们来看看怎么实现。

解决方式1:标记整理清除

套用了Old区使用的gc算法,就是将需要的元素标记,然后整理到一侧,最后删除无用元素。直接上代码:

    /**
     * 去掉数组的index为偶数的元素
     *
     * @param list list
     * @return List
     */
    private List<Integer> getNewList(List<Integer> list) {
        int index = 0;
        for (int i = 1; i < list.size(); i +=2) {
            list.set(index, list.get(i));
            index++;
        }
        return list.subList(0, index);
    }


    public static void main(String[] args) {
        TestCase testCase = new TestCase();
        List<Integer> list = Arrays.asList(0,1,2,3,4,5,6);
        List<Integer> result =  testCase.getNewList(list);
        result.forEach(System.out::println);
    }

执行结果:

1
3
5

实现起来比较简单,将index为奇数的元素挪到列表的前面位置,然后取subList即可。

解决方式2:迭代器

Iterator是JDK给集合提供用来访问和删除元素的接口,先看我写的第一版代码:

    private List<Integer> getNewListV3(List<Integer> list) {
        Iterator<Integer> iterator = list.listIterator();
        int index = 0;
        while (iterator.hasNext()) {
            iterator.next();
            if (index % 2 == 0) {
                iterator.remove();
            }
            index++;
        }
        return list;
    }

   public static void main(String[] args) {
        TestCase testCase = new TestCase();
        List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
        List<Integer> result = testCase.getNewListV3(list);
        result.forEach(System.out::println);
    }

执行结果:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.remove(AbstractList.java:161)
	at java.util.AbstractList$Itr.remove(AbstractList.java:374)
	at com.yu.java.TestCase.getNewListV3(TestCase.java:79)
	at com.yu.java.TestCase.main(TestCase.java:22)

直接抛UnsupportedOperationException了,进源码看下,才知道默认AbstractList不支持remove元素。

    /**
     * {@inheritDoc}
     *
     * <p>This implementation always throws an
     * {@code UnsupportedOperationException}.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws IndexOutOfBoundsException     {@inheritDoc}
     */
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

那我们用arrayList试下

    /**
     * @param list
     * @return
     */
    private List<Integer> getNewListV2(ArrayList<Integer> list) {
        Iterator<Integer> iterator = list.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            iterator.next();
            if (index % 2 == 0) {
                iterator.remove();
            }
            index++;
        }
        return list;
    }


    public static void main(String[] args) {
        TestCase testCase = new TestCase();
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));
        List<Integer> result = testCase.getNewListV2(list);
        result.forEach(System.out::println);
    }

执行结果:

1
3
5

顺利输出预期结果,ArrayList实现的iterator支持遍历时删除元素。我们来看下其实现原理。ArrayList的iterator内部实现较Itr,其核心类参数cursor(下一个要返回的元素的index),lastRet(上一个已返回元素的index),expectedModCount(期望修改次数 modCount是ArrayList的类变量,确保两边不会同时修改)。核心方法就是next和remove。

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
        
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            // 当取出元素后,cursor = cursor + 1。
            cursor = i + 1;
            // lastRet = cursor 加操作之前的index,也就对应上次返回元素的index。
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //调用ArrayList本身的remove方法,使用的是System.arrayCopy()方法,
                //把lastRet+1后面的元素复制到lastRet位置上。
                ArrayList.this.remove(lastRet);
                //remove掉一个元素后,为了保证访问的仍是原数组的下一个元素
                cursor = lastRet;
                // 上一个元素是被删除了,所以直接是-1,这里也表示如果要再删除的话,必须先调用next()方法。
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

源码的核心逻辑都以注释的逻辑写在代码里了。我们再根据删除偶数index的逻辑走一遍源码。

方法index(原数组)cursorlastRet
next010
remove00-1
next110
next221
remove21-1
next321
next432
remove42-1
以此类推

通过这个过程可以看出,ArrayList的Iterator在删除元素时,是通过cursor和lastRet定位下一个要访问的元素在删除后的数组中位置来解决遍历和删除同时执行的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值