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

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

1. 初始化 List 集合

// 写法一

List list = new ArrayList<>();

list.add("a");

list.add("b");

list.add("c");

// 写法二

List list = new ArrayList(){{

add("a");

add("b");

add("c");

}};

2. 遍历

方式一:for循环

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

for(int i=0; i

System.out.println(list.get(i));

}

方式二:for-each循环

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

for (String str : list){

System.out.println(str);

}

方式三:迭代器

Iterator 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 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

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 filter) {

Objects.requireNonNull(filter);

boolean removed = false;

final Iterator each = iterator();

while (each.hasNext()) {

if (filter.test(each.next())) {

each.remove();

removed = true;

}

}

return removed;

}

4. 参考

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

相关推荐

小米手环离手机最远几米,在有效范围内?如果两个距离超出范围,手机或者手环会提醒吗?
都在借势世界杯,淘宝如何吸引2亿互动?
Bet体育365提款流水

都在借势世界杯,淘宝如何吸引2亿互动?

07-13 👁️ 4621
海淘订单状态Shipped是什么意思?还会不会被砍单?
微信怎么批量删除聊天记录?教程教你快速删除记录
Bet体育365提款流水

微信怎么批量删除聊天记录?教程教你快速删除记录

09-12 👁️ 1088