ArrayList的删除姿势你都知道了吗
引言
前几天有个读者由于看了《ArrayList哪种遍历效率最好,你真的弄明白了吗?》问了个问题普通for循环ArrayList为什么不能删除连续重复的两个元素?其实这个描述是不正确的。正确的应该是普通for循环正序删除,不能删除连续的元素所以就产生了这个文章。

ArrayList删除数据的方式
我们先看下ArrayList总共有几种删除元素的方法吧。
package com.workit.demo.array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
/**
* @author:
* @Date: 2020/6/9
* @Description:
*/
public class ArrayListDelete {
public static void main(String[] args) {
Predicate<String> predicate = p -> p.equals("a") || p.equals("b");
// 可以正常删除结果正确
deleteByIterator(getList(), predicate);
// 可以正常删除结果正确
deleteByReverseOrder(getList(), predicate);
// 可以删除 结果不正确
deleteByOrder(getList(), predicate);
// 不能删除 报错java.util.ConcurrentModificationException
deleteByArrayList(getList(), predicate);
// 不能删除 报错java.util.ConcurrentModificationException
deleteByForeach(getList(), predicate);
//不能删除 报错 java.util.ConcurrentModificationException
deleteByEnhancedForLoop(getList(), predicate);
// 正常删除数据
deleteAll(getList(), predicate);
}
public static List<String> getList() {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
return list;
}
/**
* 普通for循环倒序删除
* 可以正常删除 结果正确
* @param list
* @param predicate
*/
public static void deleteByReverseOrder(List<String> list, Predicate<String> predicate) {
for (int i = list.size() - 1; i >= 0; i--) {
if (predicate.test(list.get(i))) {
list.remove(list.get(i));
}
}
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
}
/**
* 普通for循环正序删除
*可以删除 结果不正确
* @param list
* @param predicate
*/
public static void deleteByOrder(List<String> list, Predicate<String> predicate) {
for (int i = 0; i < list.size(); i++) {
if (predicate.test(list.get(i))) {
list.remove(list.get(i));
}
}
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
}
/**
* 迭代器循环,使用ArrayList的remove()方法删除
* 可以删除 结果不正确
* @param list
* @param predicate
*/
public static void deleteByArrayList(List<String> list, Predicate<String> predicate) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (predicate.test(iterator.next())) {
list.remove(iterator.next());
}
}
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
}
/**
* 迭代器循环,使用迭代器的remove()方法删除
* 可以正常删除结果正确
* @param list
* @param predicate
*/
public static void deleteByIterator(List<String> list, Predicate<String> predicate) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (predicate.test(iterator.next())) {
iterator.remove();
}
}
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
}
/**
* java8 forEach方法删除
*不能删除 报错 java.util.ConcurrentModificationException
* @param list
* @param predicate
*/
public static void deleteByForeach(List<String> list, Predicate<String> predicate) {
list.forEach(p -> {
if (predicate.test(p)) {
list.remove(p);
}
});
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
}
/**
* 增强版for循环删除
*不能删除 报错 java.util.ConcurrentModificationException
* @param list
* @param predicate
*/
public static void deleteByEnhancedForLoop(List<String> list, Predicate<String> predicate) {
for (String string : list) {
if (predicate.test(string)) {
list.remove(string);
}
}
}
}
/**
* 调用批量删除方法
* @param list
* @param predicate
*/
public static void deleteAll(List<String> list, Predicate<String> predicate) {
List<String> removeList = new ArrayList<>();
for (String string : list) {
if (predicate.test(string)) {
removeList.add(string);
}
}
list.removeAll(removeList);
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
}
下面我们来分析下为什么这些方法为什么有的可以正确删除元素,有的不可以。引用大佬们经常说的一句话源码之下无秘密那我们就把源码搞起来吧。
增强版for循环删除 && 迭代器循环使用ArrayList.remove()方法删除
- 增强版for循环删除(
deleteByEnhancedForLoop)、迭代器循环,使用ArrayList的remove()方法删除(deleteByArrayList)这两种姿势都会抛出java.util.ConcurrentModificationException他们本质都是迭代器循环,每次循环都会checkForComodification这个方法检查modCount和expectedModCount的值。
@SuppressWarnings("unchecked")
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();
}
而List的删除方法每次删除之后modCount都会进行加1操作,expectedModCount值不变还是原来的。
private void fastRemove(int index) {
modCount++; //modCount`都会进行加1操作
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
所以上面两个方法都会抛出ConcurrentModificationException异常。
java8 forEach方法删除(抛出异常)
- java8 forEach方法删除(
deleteByForeach)为什么也会抛**ConcurrentModificationException异常呢?答案还是在源码里面。
同上面一样删除一个元素后modCount进行了加1而expectedModCount没有变化。
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) { // 是不是又是这个判断
throw new ConcurrentModificationException();
}
正序删除不能删除连续元素的原因
- 可以删除但是结果不正确的方法for循环正序删除(
deleteByOrder)
先来张图吧,看图更直观。

数组删除元素后每次都需要移动。第一次删除(i=0)后b的下标就为0了,然后第二次(i=1)进行删除的时候是不是就成功的把b给遗漏了。(倒序循环删除就可以避免这种情况)那如果我们非要使用正序循环删除数据那有什么解决办法吗?办法是有的只要在删除后面把i的值进行修正下。代码如下:
for (int i = 0; i < list.size(); i++) {
if (predicate.test(list.get(i))) {
list.remove(list.get(i));
// 新增这个修正i的值
i--;
}
}
是不是又get了一个骚操作。

使用迭代器的remove()方法删除(推荐做法)
迭代器循环,使用迭代器的remove()方法删除(deleteByIterator)这个比较简单我们直接看迭代器的删除
关键代码就一行 expectedModCount = modCount
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; //调用ArrayList的删除方法后多了这么一句话。
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
这种方法也是《阿里开发手册》(需要的可以公众号回复:泰山)推荐的。

总结
上面列举了一系列的删除方法,稍不小心使用不当就踩坑里面了。这么多我也记不住啊?最好的方法就是不要边循环边删除数据。如果非要删除咧?个人建议可以使用批量删除方法(本人屡试不爽)或者迭代器的remove()方法。
结束
- 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
- 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
- 感谢您的阅读,十分欢迎并感谢您的关注。

ArrayList的删除姿势你都知道了吗的更多相关文章
- ArrayList迭代过程删除问题
一:首先看下几个ArrayList循环过程删除元素的方法(一下内容均基于jdk7): package list; import java.util.ArrayList; import java.uti ...
- MySQL 中删除的数据都去哪儿了?
不知道大家有没有想过下面这件事? 我们平时调用 DELETE 在 MySQL 中删除的数据都去哪儿了? 这还用问吗?当然是被删除了啊 那么这里又有个新的问题了,如果在 InnoDB 下,多事务并发的情 ...
- Java中ArrayList的删除元素总结
Java中循环遍历元素,一般有for循环遍历,foreach循环遍历,iterator遍历. 先定义一个List对象 List<String> list = new ArrayList&l ...
- 为什么查询出来的数据保存到Arraylist?插入删除数据为啥用LinkedList?
引言:这是我在回答集合体系时,被问到的一个问题,也是因为没有深入学习所以回答的并不是很好,所以这两天看了一下,以下是我的一些回答与学习方法. 学习方法:我们学习,系统性的学习肯定是比零散的学习更有效的 ...
- ArrayList 实现删除重复元素(元素为对象类型)
package 集合; import java.util.ArrayList;import java.util.Iterator; /* * 删除集合中的重复的元素(元素是对象形式的) * * Li ...
- ArrayList实现删除重复元素(元素不是对象类型的情况)
package 集合; import java.util.ArrayList;import java.util.Iterator; /* * 去除ArrayList里面的重复元素 * * */pub ...
- arraylist 为什么 删除元素时要使用迭代器而不能使用遍历
因为你要是遍历了,arraylist 的长度就变了,容易数组越界和下标问题 public class Test { public static void main(String[] args) ...
- ArrayList中删除null元素效率比较
package test; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; i ...
- SpringBoot图文教程7—SpringBoot拦截器的使用姿势这都有
有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1「概念+ ...
随机推荐
- CentOS 7搭建本地yum源
问题 CentOS7.1安装rpm包时提示缺失包,有些包iso里面也没有,只能从外网yum,这种情况下,可以提前yum好所需的依赖包,做成一个本地yum源.将这些文件拷贝到没联网的机器就可以使用了. ...
- Django的下载及命令
安装 命令行 pip3 install django==1.11.11 测试是否安装成功 django-admin 创建django项目 django-admin startproject 项目名称( ...
- 单体->集群->模块化->分布式微服务
开头语: 每篇一段开头语,在技术的道路中寻找文采的乐趣.(如果随笔中都是大白话勿喷,兄弟姐妹们) 单体项目 单体项目适用于小型开发,或自己来进行小项目的测试和使用. 单体项目的缺憾 多人开发项目所出现 ...
- 第8.23节 Python中使用sort/sorted排序与“富比较”方法的关系分析
一. 引言 <第8.21节 Python中__lt__.gt__等 "富比较"("rich comparison")方法用途探究>和<第8.2 ...
- 第10.2节 查看导入的Python模块
在Python中,要查看导入模块,可以使用sys.modules来查看,不过sys包含了所有导入模块包括内建模块,如果需要过滤掉内建模块甚至扩展模块,则需要对sys.modules进行一下过滤. 一. ...
- 第15.22节 PyQt(Python+Qt)入门学习:Model/View架构详解
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.简介 在PyQt和Qt中,Model/View架构是图形界面开发时用于管理数据和界面展现方式的关 ...
- Monkey的使用
1.进入adb shell 环境 在Windows环境下进入DOS界面,输入adb shell 注意:adb shell服务使用的端口是5037,如果此端口被其他进程占用时,将不能正常启动adb sh ...
- 冲刺Day3
每天举行站立式会议照片: 昨天已完成的工作: 1.完成登录注册的后台代码. 2.确定商品查找的接口. 3.尝试与数据库连接. 今天计划完成的工作: 成员 任务 高嘉淳 完成用户信息管理的部分功能 覃泽 ...
- Day4 Scrum 冲刺博客
线上会议: 昨天已完成的工作与今天计划完成的工作及工作中遇到的困难: 成员姓名 昨天完成工作 今天计划完成的工作 工作中遇到的困难 纪昂学 创建一个Tetromino类来作为7个经典形状的父类 绘制下 ...
- git学习——git下载安装
原文来至 一.集中式vs分布式 Linus一直痛恨的CVS及SVN都是集中式的版本控制系统,而Git是分布式版本控制系统,集中式和分布式版本控制系统有什么区别呢? 先说集中式版本控制系统,版本库是集中 ...