List移除元素的四种方式

List 移除某个元素

四种方式:

  • 方式一,使用 Iterator ,顺序向下,如果找到元素,则使用 remove 方法进行移除。
  • 方式二,倒序遍历 List ,如果找到元素,则使用 remove 方法进行移除。
  • 方式三,正序遍历 List ,如果找到元素,则使用 remove 方法进行移除,然后进行索引 “自减”。
  • 方式四,使用jdk1.8新增的Stream流操作
    1.Iterator 迭代器
	@Test
    public void fun9(){
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        // 获取迭代器
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            if("关羽".equals(str)){
                it.remove();
            }
        }
        System.out.println(list);
    }

2.倒序遍历

	@Test
    public void fun10(){
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        for (int i = list.size() - 1; i > 0; i--) {
            if("关羽".equals(list.get(i))){
                list.remove(i);
            }
        }
        System.out.println(list);
    }

3.正序遍历

 	@Test
    public void fun11(){
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        for (int i = 0; i < list.size(); i++) {
            if("关羽".equals(list.get(i))){
                list.remove(i);
                i--;
            }
        }
        System.out.println(list);
    }

4.Stream流操作(JDK 1.8 +)

	@Test
    public void fun8(){
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        // 筛选出不是“关羽” 的集合
        list = list.stream().filter(e -> !"关羽".equals(e)).collect(Collectors.toList());
        System.out.println("method4|list=" + list);

    }

问题:

1.为什么不能使用forEach

	@Test
    public void fun5(){
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        for (String str :list) {
            if ("张飞".equals(str)){
                list.remove(str);
            }
        }
        System.out.println(list);
    }

在这里插入图片描述
原因:
         foreach方式遍历元素的时候,是生成iterator,然后使用iterator遍历。在生成iterator的时候,会保存一个expectedModCount参数,这个是生成iterator的时候List中修改元素的次数。如果你在遍历过程中删除元素,List中modCount就会变化,如果这个modCount和exceptedModCount不一致,就会抛出异常。这个是为了安全的考虑。如果使用iterator遍历过程中,使用List修改了元素,可能会出现不正常的现象。如果使用iterator的remove方法则会正常,因为iterator的remove方法会在内部调用List的remove方法,但是会修改excepedModCount的值,因此会正常运行。

2.为什么forEach 删除倒数第二元素不会出现异常

 	@Test
    public void fun12() {
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        for (String str:list) {
            if("关羽".equals(str)){
                list.remove(str);
            }
            System.out.println(str);
        }
    }

在这里插入图片描述
仔细观察发现集合最后一个元素(“张飞”)并没有被遍历出来,因为当我们移除倒数第二个元素(“关羽”)时 cursor(游标)为 4 ,list 中size 属性的值会发生变化(5 - 1 = 4)变为 4,所以下面代码返回 false ,也就不会继续向下遍历。这也是能够正常执行的原因,因为如果继续遍历就会出现问题1 中的情况,在进行checkForComodification() 时,因为 modCount 发生了变化,而expectedModCount 并没有发生变化,所以会出现 ConcurrentModificationException异常。

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();
            }
        }
 public boolean hasNext() {
            return cursor != size;
        }
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];
        }
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

3 普通正序 for 循环为什么要 i –

因为遍历过程中进行remove 操作时,该位置后面的元素会挤到前面来,这时候会发生一种情况就是原来元素的位置会被他后面的元素取代,而该位置已经遍历过了,所以该元素不会背遍历。 所以要进行 i-- 操作从该位置重新遍历。

	@Test
    public void fun11(){
        List<String> list = new ArrayList<>();
        list.add("赵云");
        list.add("黄忠");
        list.add("马超");
        list.add("关羽");
        list.add("张飞");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
            if("关羽".equals(list.get(i))){
                list.remove(i);
            }
        }
        System.out.println(list);
    }

就是下面的情况 “张飞” 不见了…
在这里插入图片描述

4 为什么倒序for 循环可以

当我们倒序遍历元素的时候,无论删除元素之后的元素怎么移动,之前的元素对应的索引(index)是不会发生变化的,所以在删除元素的时候不会发生问题。