for循环删除元素竟然让一个小白给上了一课

上周给新来的同学讲了个需求,将一个过滤集合中不符合条件的元素,讲到实现方法的时候,遍历方式中,同学尝试了普通for循环和增强性for循环,不过都不太靠谱。来看下例子:

普通for循环

package cn.com.test;

import java.util.ArrayList;
import java.util.List;


public class Demo2 {
    public static void main(String[] args) {
        List<String> list = new  ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        //遍历删除不是C的元素
        for (int i = 0; i <list.size() ; i++) {
            if(!"c".equals(list.get(i))){
                list.remove(list.get(i));
            }
        }
      System.out.print(list);

    }
}

打印结果:

[b, c, e]

结果那么奇怪,其实仔细一看就知道其中的问题在哪,list每次删除一个元素,list的长度都应缩短,而此时的i的大小是根据list的长度判断的,当i增长到3时,此时list还有3个元素,循环就停止了。来看图说话:

有没有解决办法呢?

package cn.com.test;

import java.util.ArrayList;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        List<String> list1 = new  ArrayList<>();
        list1.add("a");
        list1.add("b");
        list1.add("c");
        list1.add("d");
        list1.add("e");
        for (int i = list1.size()-1; i >=0 ; i--) {
            if(!"c".equals(list1.get(i))){
                list1.remove(list1.get(i));
            }
        }
        System.out.print(list1);
    }
}
[c]

 从结果来看是解决了问题,不过还是看着比较蹩脚。

 增强for循环

package cn.com.test;

import java.util.ArrayList;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        List<String> list = new  ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        for (String str:list) {
            if(!"c".equals(str))
            list.remove(str);
        }
        System.out.print(list);
    }
}

 结果报错了:ConcurrentModificationException

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at cn.com.test.Demo2.main(Demo2.java:14)

for循环是通过迭代器Iterator实现的,从报错的内容可以看出,在对ArrayList进行修改时抛出的错误。查询ArrayList源码可以看出Iterator是以内部类的方式实现的。hasNext方法是判断容器中是否还有元素未被访问过,删除元素调用的是list本身的remove方法而不是迭代器本身的remove方法,会导致执行next方法校验变量expectedModCount 和modCount时不一致,所以报错ConcurrentModificationException,这个方式被pass了

 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;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        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 = i + 1;
            return (E) elementData[lastRet = i];
        }

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

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

    final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

迭代器遍历 

最稳妥的方法还是直接用迭代器遍历,使用迭代器自由的remove方法删除元素

package cn.com.test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        List<String> list = new  ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            if(!"c".equals(next)){
                iterator.remove();
            }
        }
        System.out.print(list);
    }
}

执行结果:

[c]

 不过新同学在调试过程中又出现了意外,将如下代码注释掉后竟造成了死循环。

  String next = iterator.next();
       if(!"c".equals(next)){
             iterator.remove();
       }

 不过通过分析迭代器源码就可以看出,hasnext方法只是用来判断是否还有元素未被扫描,真正移动元素的的方法是next,next元素会修改被扫描元素的个数,源码如上图。不过这只使用于单线程的环境,多线程环境就需要考虑加锁来处理了。

虽然整个程序虽然看似简单,但是对于不怎么关注实现原理的同学来说真会是一头雾水。也给我上了一课,写代码只是招式,原理层的才是口诀心法。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值