四种方式:
- 方式一,使用 Iterator ,顺序向下,如果找到元素,则使用 remove 方法进行移除。
- 方式二,倒序遍历 List ,如果找到元素,则使用 remove 方法进行移除。
- 方式三,正序遍历 List ,如果找到元素,则使用 remove 方法进行移除,然后进行索引 “自减”。
- 方式四,使用jdk1.8新增的Stream流操作
1.Iterator 迭代器
@Test public void fun9(){ List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); // 获取迭代器 Iterator<String> it = list.iterator(); while(it.hasNext()){ String str = it.next(); if("关羽".equals(str)){ it.remove(); } } System.out.println(list); }
2.倒序遍历
@Test public void fun10(){ List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); for (int i = list.size() - 1; i > 0; i--) { if("关羽".equals(list.get(i))){ list.remove(i); } } System.out.println(list); }
3.正序遍历
@Test public void fun11(){ List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); for (int i = 0; i < list.size(); i++) { if("关羽".equals(list.get(i))){ list.remove(i); i--; } } System.out.println(list); }
4.Stream流操作(JDK 1.8 +)
@Test public void fun8(){ List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); // 筛选出不是“关羽” 的集合 list = list.stream().filter(e -> !"关羽".equals(e)).collect(Collectors.toList()); System.out.println("method4|list=" + list); }
问题:
1.为什么不能使用forEach
@Test public void fun5(){ List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); for (String str :list) { if ("张飞".equals(str)){ list.remove(str); } } System.out.println(list); }
原因:
foreach方式遍历元素的时候,是生成iterator,然后使用iterator遍历。在生成iterator的时候,会保存一个expectedModCount参数,这个是生成iterator的时候List中修改元素的次数。如果你在遍历过程中删除元素,List中modCount就会变化,如果这个modCount和exceptedModCount不一致,就会抛出异常。这个是为了安全的考虑。如果使用iterator遍历过程中,使用List修改了元素,可能会出现不正常的现象。如果使用iterator的remove方法则会正常,因为iterator的remove方法会在内部调用List的remove方法,但是会修改excepedModCount的值,因此会正常运行。
2.为什么forEach 删除倒数第二元素不会出现异常
@Test public void fun12() { List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); for (String str:list) { if("关羽".equals(str)){ list.remove(str); } System.out.println(str); } }
仔细观察发现集合最后一个元素(“张飞”)并没有被遍历出来,因为当我们移除倒数第二个元素(“关羽”)时 cursor(游标)为 4 ,list 中size 属性的值会发生变化(5 - 1 = 4)变为 4,所以下面代码返回 false ,也就不会继续向下遍历。这也是能够正常执行的原因,因为如果继续遍历就会出现问题1 中的情况,在进行checkForComodification() 时,因为 modCount 发生了变化,而expectedModCount 并没有发生变化,所以会出现 ConcurrentModificationException异常。
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(); } }
public boolean hasNext() { return cursor != size; }
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]; }
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
3 普通正序 for 循环为什么要 i –
因为遍历过程中进行remove 操作时,该位置后面的元素会挤到前面来,这时候会发生一种情况就是原来元素的位置会被他后面的元素取代,而该位置已经遍历过了,所以该元素不会背遍历。 所以要进行 i-- 操作从该位置重新遍历。
@Test public void fun11(){ List<String> list = new ArrayList<>(); list.add("赵云"); list.add("黄忠"); list.add("马超"); list.add("关羽"); list.add("张飞"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); if("关羽".equals(list.get(i))){ list.remove(i); } } System.out.println(list); }
就是下面的情况 “张飞” 不见了…
4 为什么倒序for 循环可以
当我们倒序遍历元素的时候,无论删除元素之后的元素怎么移动,之前的元素对应的索引(index)是不会发生变化的,所以在删除元素的时候不会发生问题。