集合操作进阶:关于移除列表元素的那点事

2024-06-02 05:04

本文主要是介绍集合操作进阶:关于移除列表元素的那点事,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

日常开发中,难免会对集合中的元素进行移除操作,如果对这方面不熟悉的话,就可能遇到 ConcurrentModificationException,那么,如何优雅地进行元素删除?以及其它方式为什么不行?

数据初始化
public static final List<String> list = new ArrayList<>();static {list.add("java");list.add("mysql");list.add("redis");list.add("spring");list.add("linux");list.add("git");
}
实现方式
方式一:普通 for
 @Testpublic void testRemoveFor() {list.add(2, "redis");System.out.println(list);for (int i = 0; i < list.size(); i++) {if ("redis".equals(list.get(i))) {list.remove(i);}}System.out.println(list);
}

这种方式虽然不会报错,但是会导致数据出现问题,比如 list: [java, mysql, redis, redis, spring, linux, git]

由于存在两个连续的 redis,当删除第一个时,后面的元素会向前填充,而迭代索引是一直增加的(即 i ),所以第二个 redis 没有经过检查就直接跳过了,导致最终数据为:[java, mysql, redis, spring, linux, git]

IDEA 其实也给出了我们提示(如下图):Suspicious ‘List.remove()’ in loop;想必就是因为这个原因。

在这里插入图片描述

方式二:增强 for
@Test
public void testRemoveForEach() {for (String str : list) {if ("redis".equals(str)) {list.remove(str);}}System.out.println(list);
}

这种方式就直接报错了:java.util.ConcurrentModificationException

将 .class 文件反编译后以上代码对应如下(将 .class 用 IDEA 打开即可):

Iterator var1 = list.iterator();
while(var1.hasNext()) {String str = (String)var1.next();if ("redis".equals(str)) {list.remove(str);}
}

从以上代码可以看出,for-each 实际上就是使用迭代器进行遍历元素,当在 for-each 中通过 java.util.ArrayList#remove 删除元素时,迭代器内部其实是不知道的,所以在执行 java.util.ArrayList.Itr#next() 操作时,会检查内部状态(modCount)是否一致,不一致则抛出。java.util.ConcurrentModificationException

方式三:迭代器
@Test
public void testRemoveIterator() {Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String str = iterator.next();if ("redis".equals(str)) {iterator.remove();}}System.out.println(list);
}

这种方式与上面 testRemoveForEach 相比,其实就只是将内部的 java.util.ArrayList#remove 换成了迭代器内部remove 方法,既然用的是迭代器自己的,那迭代器内部肯定就能够知道列表发生了变化,从而直接更新其内部状态,所以就不会报错了。

 /*** lastRet: 最后一次返回元素的索引* cursor:下一个返回元素的索引*/public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {// 调用 ArrayList 内部的 remove 进行删除元素,即 java.util.ArrayList#removeArrayList.this.remove(lastRet);// 将最后一次返回元素的索引赋值给下一个返回元素的索引(因为当前元素被删除后,后面的元素前移了)cursor = lastRet;lastRet = -1;// remove 之后,modCount 会加一,所以需要将 modCount 赋值给 expectedModCount,从而保持一致expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}
方式四:反向遍历
@Test
public void testRemoveForDesc() {for (int i = list.size() - 1; i >= 0; i--) {if ("redis".equals(list.get(i))) {list.remove(i);}}System.out.println(list);
}

通过倒序的方式遍历列表并删除元素,虽然这种方式也能够实现删除元素不报错,但从性能上考虑,当删除的元素比较多时,时间复杂度会变成 O(n^2),所以这种方式不建议使用。

方式五:removeIf
@Test
public void testRemoveIf() {list.removeIf("redis"::equals);System.out.println(list);
}

如果使用的是 JDK 1.8 及以上,建议使用这种方式删除元素,代码简洁优雅

查看源码,removeIf 底层其实就是使用迭代器的方式进行删除。

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

这篇关于集合操作进阶:关于移除列表元素的那点事的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023092

相关文章

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

MySQL基本表查询操作汇总之单表查询+多表操作大全

《MySQL基本表查询操作汇总之单表查询+多表操作大全》本文全面介绍了MySQL单表查询与多表操作的关键技术,包括基本语法、高级查询、表别名使用、多表连接及子查询等,并提供了丰富的实例,感兴趣的朋友跟... 目录一、单表查询整合(一)通用模版展示(二)举例说明(三)注意事项(四)Mapper简单举例简单查询

Nginx概念、架构、配置与虚拟主机实战操作指南

《Nginx概念、架构、配置与虚拟主机实战操作指南》Nginx是一个高性能的HTTP服务器、反向代理服务器、负载均衡器和IMAP/POP3/SMTP代理服务器,它支持高并发连接,资源占用低,功能全面且... 目录Nginx 深度解析:概念、架构、配置与虚拟主机实战一、Nginx 的概念二、Nginx 的特点

MySQL 数据库进阶之SQL 数据操作与子查询操作大全

《MySQL数据库进阶之SQL数据操作与子查询操作大全》本文详细介绍了SQL中的子查询、数据添加(INSERT)、数据修改(UPDATE)和数据删除(DELETE、TRUNCATE、DROP)操作... 目录一、子查询:嵌套在查询中的查询1.1 子查询的基本语法1.2 子查询的实战示例二、数据添加:INSE

Python列表去重的9种方法终极指南

《Python列表去重的9种方法终极指南》在Python开发中,列表去重是一个常见需求,尤其当需要保留元素原始顺序时,本文为大家详细介绍了Python列表去重的9种方法,感兴趣的小伙伴可以了解下... 目录第一章:python列表去重保持顺序方法概述使用字典去重(Python 3.7+)使用集合辅助遍历性能

Linux服务器数据盘移除并重新挂载的全过程

《Linux服务器数据盘移除并重新挂载的全过程》:本文主要介绍在Linux服务器上移除并重新挂载数据盘的整个过程,分为三大步:卸载文件系统、分离磁盘和重新挂载,每一步都有详细的步骤和注意事项,确保... 目录引言第一步:卸载文件系统第二步:分离磁盘第三步:重新挂载引言在 linux 服务器上移除并重新挂p

使用Python在PDF中绘制多种图形的操作示例

《使用Python在PDF中绘制多种图形的操作示例》在进行PDF自动化处理时,人们往往首先想到的是文本生成、图片嵌入或表格绘制等常规需求,然而在许多实际业务场景中,能够在PDF中灵活绘制图形同样至关重... 目录1. 环境准备2. 创建 PDF 文档与页面3. 在 PDF 中绘制不同类型的图形python

Java 操作 MinIO详细步骤

《Java操作MinIO详细步骤》本文详细介绍了如何使用Java操作MinIO,涵盖了从环境准备、核心API详解到实战场景的全过程,文章从基础的桶和对象操作开始,到大文件分片上传、预签名URL生成... 目录Java 操作 MinIO 全指南:从 API 详解到实战场景引言:为什么选择 MinIO?一、环境

在DataGrip中操作MySQL完整流程步骤(从登录到数据查询)

《在DataGrip中操作MySQL完整流程步骤(从登录到数据查询)》DataGrip是JetBrains公司出品的一款现代化数据库管理工具,支持多种数据库系统,包括MySQL,:本文主要介绍在D... 目录前言一、登录 mysql 服务器1.1 打开 DataGrip 并添加数据源1.2 配置 MySQL

Go语言中如何进行数据库查询操作

《Go语言中如何进行数据库查询操作》在Go语言中,与数据库交互通常通过使用数据库驱动来实现,Go语言支持多种数据库,如MySQL、PostgreSQL、SQLite等,每种数据库都有其对应的官方或第三... 查询函数QueryRow和Query详细对比特性QueryRowQuery返回值数量1个:*sql