Java 集合(List、Set)遍历、判断、删除元素时的小陷阱

开发中,常有场景:遍历集合,依次判断是否符合条件,如符合条件则删除当前元素。

不知不觉中,有些陷阱,不知你有没有犯。

1. 一、漏网之鱼-for循环递增下标方式遍历集合,并删除元素

如果你用for循环递增下标方式遍历集合,在遍历过程中删除元素,你可能会遗漏了某些元素。说那么说可能也说不清楚,看以下示例:

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

public class ListTest_Unwork {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);

        String temp = null;
        for (int i = 0; i < list.size(); i++) {
            temp = list.get(i);

            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

日志打印:

Original list : [1, 2, 3, 4, 5]
Check for 1
Check for 2
Check for 3
Check for 5
Removed  list : [1, 2, 4, 5]

如日志所见,其中值为4的元素并未经过判断,漏网之鱼。

解决方法为以下两个(但一般不建议我们在遍历中用不是遍历本身的函数删除元素,见下节关于“ConcurrentModificationException”的内容):

1、对于此情况,我一般都从后面开始遍历,以避免问题:

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

public class ListTest_Work {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        String temp = null;
        for (int i = list.size() - 1; i >= 0; i--) {
            temp = list.get(i);

            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

2、直接从新创建一个集合,重新摆放,但消耗内存,慎用:

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

public class ListTest_Work2 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        List<String> tempList = new ArrayList<String>();
        for (String temp : list) {
            System.out.println("Check for " + temp);
            if (!"3".equals(temp)) {
                tempList.add(temp);
            }
        }
        System.out.println("Removed  list : " + tempList);
    }

}

2. 二、ConcurrentModificationException异常-Iterator遍历集合过程中用其他手段(或其他线程)操作元素

ConcurrentModificationException是Java集合的一个快速报错(fail-fast)机制,防止多个线程同时修改同一个集合的元素。在用Iterator遍历集合时,如果你用其他手段(非Iterator自身手段)操作集合元素,就会报ConcurrentModificationException。

不信?用Iterator方式或简写的for(Object o : list) {}方式,遍历集合,修改元素时会报异常:

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

public class ListTest2_Unwork {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        for (String temp : list) {
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

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

public class ListTest3_Unwork {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        Iterator<String> i = list.iterator();
        String temp = null;
        while (i.hasNext()) {
            temp = i.next();
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

日志:

Original list : [1, 2, 3, 4, 5]

Check for 1
Check for 2
Check for 3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at ListTest3_Unwork.main(ListTest3_Unwork.java:20)

在删除元素“3”时,会报异常。

对于此情况,需要用iterator的remove方法替代,结果是妥妥的:

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

public class ListTest3_Work {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        Iterator<String> i = list.iterator();
        String temp = null;
        while (i.hasNext()) {
            temp = i.next();
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                i.remove();
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

延伸个小问题,为什么for(Object o : list) {}方式遍历集合,现象和Iterator方式一样,都会报错呢?

答:这是因为Java的糖语法,“for(Object o : list) {}方式”只是Java语言用“易用性糖衣”吸引你的手段,本质上,它也是Iterator。不信,你写下下面这段程序,反编译看看就清楚了:

package com.nichagil.test.forloop;

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

public class ForTester {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");

        for (String s : list) {
            list.remove(s);
            System.out.println(s);
        }
    }

}

package com.nichagil.test.forloop;

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

public class ForTester {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");

        for (String s : list) {
            list.remove(s);
            System.out.println(s);
        }
    }

}

反编译后是这样的:

package com.nichagil.test.forloop;

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

public class ForTester {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("a");
        Iterator arg2 = list.iterator();

        while (arg2.hasNext()) {
            String s = (String) arg2.next();
            list.remove(s);
            System.out.println(s);
        }

    }
}

package com.nichagil.test.forloop;

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

public class ForTester {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("a");
        Iterator arg2 = list.iterator();

        while (arg2.hasNext()) {
            String s = (String) arg2.next();
            list.remove(s);
            System.out.println(s);
        }

    }
}
时间: 2024-10-27 06:51:09

Java 集合(List、Set)遍历、判断、删除元素时的小陷阱的相关文章

Java HashMap 如何正确遍历并删除元素

(一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K, V> myHashMap; for (Map.entry<K, V> item : myHashMap.entrySet()){ K key = item.getKey(); V val = item.getValue(); //todo with key and val //WARNI

Java集合02----LinkedList的遍历方式及应用

                                          Java集合02----LinkedList的遍历方式及应用 前面已经学习了ArrayList的源码,为了学以致用,故列举一些ArrayList的遍历方式及应用.JDK源码学习系列05----LinkedList 1.LinkedList的遍历方式 a.一般的for循环(随机访问) int size = list.size(); for (int i=0; i<size; i++) { list.get(i);

java实现二叉树及遍历、删除

java实现二叉树及遍历.删除 个人网站:多猫影视[可以看各大网站的VIP视频]www.duomao.xyz 二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态: (1)空二叉树——如图(a): (2)只有一个根结点的二叉树——如图(b): (3)只有左子树——如图(c): (4)只有右子树——如图(d): (5)完全二叉树——如图(e). 注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形.[1] 类型 (1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它

Java集合01----ArrayList的遍历方式及应用

                                             Java集合01----ArrayList的遍历方式及应用 1.ArrayList的遍历方式 a.一般for循环(随机访问) Integer value = null; int size = list.size(); for (int i=0; i<size; i++) { value = (Integer)list.get(i); } b.增强型for循环(for-each) Integer value

java中在for循环中remove元素时的陷阱

java.util.ConcurrentModificationException 如果删空了,会报上面这个异常 输出结果如下:remove前集合数据:a,a,b,a,a, remove后集合数据:a,b,a, 为什么会出现这种情况?原因是集合的大小是动态变化的,在删除第1个值为“a”的元素后,集合的大小已经发生了改变,但是i的值在删除后继续执行了加1操作,此时已经跳过了“a”元素后的元素(即原集合中第2个“a”元素). 如下删除集合中值为“a”的元素时: import java.util.*;

C#中遍历当前所有进程时存在的陷阱

有时候我们希望我们所写的exe在机器上同一时间只能有一个实例进行运行.通常我们会采取遍历当前所有的进程,如果有的进程exe所在的物理路径就是当前exe的物理路径的话,那么说明这个exe已经启动过一次了,就不再启动了.但是注意,这里有一些需要注意的小细节. 有时候对于编写一个应用程序的守护进程的时候,也要用到这个遍历方法.即我需要另外启动一个进程,这个进程启动后,要查看所有的进程,看看其所检测的进程是否已经启动.那么怎么判断遍历的那个进程是否是其目标进程呢?那么就需要看这个进程exe所在的路径是否

Java - List遍历、判断、删除元素时的陷阱

开发中,常有“遍历集合,依次判断是否符合条件,如符合条件则删除当前元素”的场景,有一些陷阱常犯. 漏网之鱼 import java.util.ArrayList; import java.util.List; public class ListTest_Unwork { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"

集合遍历过程iterator, 添加删除元素报异常

list  set  遍历过程中添加或者删除元素,报异常. 使用iterator 也会报异常 ConcurrentModificationException remove只能用迭代器的remove,而不能用集合的remove方法,iterator的remove会维护索引的一致性 iterator it = list.iterator(); while(it.hasnext(0){ obj = it.next(); it.remove(); } 用java.util.concurrent中的类代替

List的遍历和删除元素

/** * 遍历list的方法 * @param args */ public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("111"); list.add("222"); list.add("333"); list.add("222"); list.add("4