List.remove()的使用注意
今天修改一个bug,需要取一个List和一个Set的交集,使用了双重循环。想着提高循环效率,每加入一个交集中的元素,就将List中的元素删除,减少不必要的循环。结果直接调用了List的remove()方法,抛出了java.util.ConcurrentModificationException异常。这时才忽然记起之前看过的List循环中使用remove()方法要特别注意,尤其是forEach的循环。
不使用forEach的循环
使用常规的for循环写法,代码如下:
public static void main(String [] args){
List<String> list = new ArrayList<String>();
list.add("111");
list.add("111");
list.add("111");
list.add("333");
list.add("333");
for (int i = 0; i < list.size(); i++){
if ("111".equals(list.get(i))){
list.remove("111");
// i--;不加这句会少删除一个111
}
System.out.println(list.size());
}
}
先说说list的remove()方法,该方法有两个,一个是remove(Object obj),另一个是remove(int index)。根据参数很容易理解,而这里要说的是remove(obj)会删除list中的第一个该元素,remove(index)会删除该下标的元素。调用一次remove方法,会使list中的所有该删除元素后面的元素前移。看看一个remove(Object obj)的源码:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
remove是可以删除null的,但一般情况都是走else路径,再看看faseRemove方法:
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // Let gc do its work
}大专栏 List.remove()的使用注意>
System.arraycopy就是将list中删除元素的后面迁移,暂时没继续深入了解。总得来说,这样的操作是因为没有对变化的list做出相应的下标处理而产生了错误的结果,但是并没有产生异常。
使用forEach循环
错误的使用代码如下:
public static void main(String [] args){
List<String> list = new ArrayList<String>();
list.add("111");
list.add("111");
list.add("111");
list.add("333");
list.add("333");
for (String str : list){
if("111".equals(str)){
list.remove(str);//抛出异常java.util.ConcurrentModificationException
}
}
}
这样使用会抛出java.util.ConcurrentModificationException。在forEach中,遍历的集合都必须实现Iterable接口(数组除外)。而forEach的写法实际是对Iterator遍历的简写,类似于以下代码:
public void display(){
for(String s : strings){
System.out.println(s);
}
Iterator<String> iterator = strings.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}
上面的forEach和下面的Iterator迭代器效果一样,涉及到编译原理的一些内容,就不深究了。可以理解为forEach将其中的集合转为了迭代器进行遍历,而该迭代器内部有一个next()方法,代码如下:
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
可以看到在使用next()方法获取下一个元素的之前会先检查迭代器修改次数,在我们使用ArrayList的remove()方法删除元素时,实际只修改了modCount,这样就会造成modCount和expectedModCount不相等,从而抛出异常。而使用迭代器本身的remove()方法则不会,因为Iterator本身的remove()方法会同时修改modCount和expectedModCount。
引用一段网上的解释:
Iterator是工作在一个独立的线程中,并且拥有一个mutex锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照fail-fast原则Iterator会马上抛出java.util.ConcurrentModificationException异常。所以Iterator在工作的时候是不允许被迭代的对象被改变的。但你可以使用Iterator本身的方法remove()来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
最后总结一下就是,forEach将List转为了Iterator,删除元素就需要使用Iterator的remove()方法,错误地使用了List.remove()方法就会抛出异常。
参考
List.remove()的使用注意的更多相关文章
- EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解
前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...
- [LeetCode] Remove K Digits 去掉K位数字
Given a non-negative integer num represented as a string, remove k digits from the number so that th ...
- [LeetCode] Remove Duplicate Letters 移除重复字母
Given a string which contains only lowercase letters, remove duplicate letters so that every letter ...
- [LeetCode] Remove Invalid Parentheses 移除非法括号
Remove the minimum number of invalid parentheses in order to make the input string valid. Return all ...
- [LeetCode] Remove Linked List Elements 移除链表元素
Remove all elements from a linked list of integers that have value val. Example Given: 1 --> 2 -- ...
- [LeetCode] Remove Duplicates from Sorted List 移除有序链表中的重复项
Given a sorted linked list, delete all duplicates such that each element appear only once. For examp ...
- [LeetCode] Remove Duplicates from Sorted List II 移除有序链表中的重复项之二
Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numb ...
- [LeetCode] Remove Duplicates from Sorted Array II 有序数组中去除重复项之二
Follow up for "Remove Duplicates":What if duplicates are allowed at most twice? For exampl ...
- [LeetCode] Remove Element 移除元素
Given an array and a value, remove all instances of that value in place and return the new length. T ...
- [LeetCode] Remove Duplicates from Sorted Array 有序数组中去除重复项
Given a sorted array, remove the duplicates in place such that each element appear only once and ret ...
随机推荐
- cassandra 系统分析 架构
cassandra cassandra是无中心节点的列式数据库 集群管理: 使用gossip算法,最终每个节点都知道集群中的所有节点信息,新增一个节点,新节点发送上线消息, 其他节点 ...
- [Scoi2014]方伯伯的OJ(动态开点splay)
开始没看数据范围差点以为是这题了:https://www.cnblogs.com/hfctf0210/p/10911340.html 然后看到n<=1e8,怎么这么大? 所以这题需要用动态开点线 ...
- python 拆解包
Python 拆解包 转自:https://www.jianshu.com/p/22c538a58bcc python中的解包可以这样理解:一个list是一个整体,想把list中每个元素当成一个个个体 ...
- CodeForces 993B Open Communication(STL 模拟)
https://codeforces.com/problemset/problem/993/b 这题不难,暴力就能过,主要是题意太难懂了 题意: 现在有两个人,每个人手中有一对数,第一个人手中的数是n ...
- C#.NET中的ToString()数字格式化
数字格式字符串-----货币-----.ToString("C");.ToString("c");例 2.5.ToString("c") - ...
- ubuntu16.04更换 apt-get 阿里源
deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties deb ht ...
- Convert between Unix and Windows text files - IU Knowledge Base from: https://kb.iu.edu/d/acux
vi. To input the ^M character, press Ctrl-v , and then press Enter or return . In vim, use :set ff=u ...
- SAP 配置表根据输入的值带出描述
在SAP客制功能需求中,为了程式的灵活配置采用配置表的形成,使后期使用中不需要更改源代码实现功能的增加.在配置表的使用过程中,有时候会有这样的需求:在配置中输入或选择了编码,根据编码带出描述.以下详细 ...
- [LC] 1055. Shortest Way to Form String
From any string, we can form a subsequence of that string by deleting some number of characters (pos ...
- Helvetic Coding Contest 2019 差A3 C3 D2 X1 X2
Helvetic Coding Contest 2019 A2 题意:给一个长度为 n 的01序列 y.认为 k 合法当且仅当存在一个长度为 n 的01序列 x,使得 x 异或 x 循环右移 k 位的 ...