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