1. 初始化 List 集合
// 写法一
List
list.add("a");
list.add("b");
list.add("c");
// 写法二
List
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 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 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 super E> filter) { Objects.requireNonNull(filter); boolean removed = false; final Iterator while (each.hasNext()) { if (filter.test(each.next())) { each.remove(); removed = true; } } return removed; } 4. 参考 【Java面试题】List如何一边遍历,一边删除?