【集合】遍历 List 集合的几种方式

1. 初始化 List 集合

// 写法一
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

// 写法二
List<String> list = new ArrayList(){{
  add("a";
  add("b";
  add("c";
}};

2. 遍历

方式一:for循环

最基础的遍历方式:for循环,指定下标位置,使用 List 集合的 get(i) 方法来获取元素。

for(int i=0; i<list.size(); i++){
    System.out.println(list.get(i));
}

方式二:for-each循环

较为简洁的遍历方式:for-each循环,只能顺序遍历,不能对某一个指定元素进行操作。(这种方法在遍历数组和 Map 集合的时候同样适用)

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

方式三:迭代器

Iterator<String> itr = list.iterator();
while(itr.hasNext()){
    String str = itr.next();
    System.out.println(str);
}

方式四:forEach + Lambda表达式

list.forEach((str)->{
    System.out.println(str);
});

3. List 如何实现一边遍历,一边删除?

在阿里的 Java 编程规约中有一条:【强制】不要在 for-each 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果是并发操作,需要对 Iterator 对象加锁。

错误做法

代码:在 for-each 循环中调用 list.remove(e) 方法。

for (String str : list){
    if(str.equals("a")) list.remove(str);
}

System.out.println(list);

报错信息

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at Demo01.main(Demo01.java:37)

报错原因:for-each 循环在实际执行时,其实使用的是 Iterator,其中的核心方法是 hasNext()next()

1、Iterator 中定义了如下字段。

  • modCount:指 List 实际的修改次数
  • expectedModCount 指 List 预计的修改次数
  • cursor:下一个元素的索引
int expectedModCount = modCount;
int cursor;       // index of next element to return

2、Iterator 中的 next() 源码。

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];
}

3、Iterator 中的 checkForComodification() 源码。

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException(); // 报错信息中抛出的异常来自这里
}

可以看出, next() 方法的第一行代码就是调用 checkForComodification() 方法,而该方法的核心逻辑是对 modCount 和 expectedModCount 这两个变量进行比较。

在上面的错误做法示例中,一开始 modCount 和 expectedModCount 的值都是3(因为我们初始化的时候添加了3个元素,也就是实际修改了3次),所以读取第一个元素 a 的时候是没问题的,但是我们执行了 list.remove(str); 之后,modCount 的值就被修改成了4,调用 List 的 remove() 方法只会增加 modCount 的值,而不会增加 expectedModCount。所以在第二次获取元素 b 时,modCount 和 expectedModCount 的值就不相等了,所以抛出了 java.util.ConcurrentModificationException 异常。

注意:

for-each 的实际执行逻辑(使用迭代器):hasNext() —>next() —>若满足条件,调用remove() —>循环直到 hasNext() 为false,结束循环。

上面的错误做法,如果删除的是倒数第2个元素,并不会报错。比如有一个列表 [a,b,c,d] ,使用 for-each 遍历列表,当调用 next() 获取倒数第2个元素,也就是元素 c 后,cursor(指向下一个待获取元素)的值就变为3,然后调用 remove() 删除倒数第2个元素,删除之后集合的 size 就减一变为 3,则 下一轮 hasNext() 为 false,不会遍历最后一个元素,也就不会调用 next(),因此也不会执行 checkForComodification(),从而不会报错。

hasNext() 源码如下:

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

正确做法一:使用Iterator的remove()方法

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
    String str = iterator.next();
    if(str.equals("a")) iterator.remove();
}

System.out.println(list);

为什么使用 iterator.remove(); 就可以呢?

让我们看下它的源码:

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();
    }
}

可以看出,每次删除一个元素,都会将 modCount 的值重新赋值给 expectedModCount,这样2个变量就相等了,不会触发 java.util.ConcurrentModificationException 异常。

正确做法二:使用for循环正序遍历

for(int i=0; i<list.size(); i++){
    if(list.get(i).equals("a")){
        list.remove(i);
        i--; // 删除元素后,要修正下标的值
    }
}

System.out.println(list);

正确做法三:使用for循环倒序遍历

for (int i = list.size()-1; i>=0; i--){
    if(list.get(i).equals("a")){
        list.remove(i);
    }
}
System.out.println(list);

这种实现方式和使用for循环正序遍历类似,不过不用再修正下标。

正确做法四:使用removeIf()方法(推荐)

list.removeIf((str)->"a".equals(str));

removeIf() 方法的源码,会发现其底层也是用的 Iterator 的 remove() 方法:

default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

4. 参考

【Java面试题】List如何一边遍历,一边删除?

  • 25
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值