博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】ArrayList循环遍历并删除元素的常见陷阱
阅读量:5079 次
发布时间:2019-06-12

本文共 3563 字,大约阅读时间需要 11 分钟。

转自:

在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug。不妨把这个问题当做一道面试题目,我想一定能难道不少的人。今天就给大家说一下在ArrayList循环遍历并删除元素的问题。首先请看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 
java
 
.
 
util
 
.
 
ArrayList
 
;
public
 
class
 
ArrayListRemove
{
public
 
static
 
void
 
main
 
(
 
String
 
[
 
]
 
args
 
)
{
ArrayList
 
<
 
String
 
>
 
list
 
=
 
new
 
ArrayList
 
<
 
String
 
>
 
(
 
)
 
;
list
 
.
 
add
 
(
 
"a"
 
)
 
;
list
 
.
 
add
 
(
 
"b"
 
)
 
;
list
 
.
 
add
 
(
 
"b"
 
)
 
;
list
 
.
 
add
 
(
 
"c"
 
)
 
;
list
 
.
 
add
 
(
 
"c"
 
)
 
;
list
 
.
 
add
 
(
 
"c"
 
)
 
;
remove
 
(
 
list
 
)
 
;
 
for
 
(
 
String
 
s
 
:
 
list
 
)
{
System
 
.
 
out
 
.
 
println
 
(
 
"element : "
 
+
 
s
 
)
 
;
}
}
public
 
static
 
void
 
remove
 
(
 
ArrayList
 
<
 
String
 
>
 
list
 
)
{
// TODO:
}
}

如果要想删除list的b字符,有下面两种常见的错误例子:

错误写法实例一:

1
2
3
4
5
6
7
8
9
10
11
public
 
static
 
void
 
remove
 
(
 
ArrayList
 
<
 
String
 
>
 
list
 
)
{
for
 
(
 
int
 
i
 
=
 
0
 
;
 
i
 
<
 
list
 
.
 
size
 
(
 
)
 
;
 
i
 
++
 
)
{
String
 
s
 
=
 
list
 
.
 
get
 
(
 
i
 
)
 
;
if
 
(
 
s
 
.
 
equals
 
(
 
"b"
 
)
 
)
{
list
 
.
 
remove
 
(
 
s
 
)
 
;
}
}
}

错误的原因:这种最普通的循环写法执行后会发现第二个“b”的字符串没有删掉。

错误写法实例二:

1
2
3
4
5
6
7
8
9
10
public
 
static
 
void
 
remove
 
(
 
ArrayList
 
<
 
String
 
>
 
list
 
)
{
for
 
(
 
String
 
s
 
:
 
list
 
)
{
if
 
(
 
s
 
.
 
equals
 
(
 
"b"
 
)
 
)
{
list
 
.
 
remove
 
(
 
s
 
)
 
;
}
}
}

错误的原因:这种for-each写法会报出著名的并发修改异常:java.util.ConcurrentModificationException。

先解释一下实例一的错误原因。翻开JDK的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
 
;
}

一般情况下程序的执行路径会走到else路径下最终调用faseRemove方法:

1
2
3
4
5
6
7
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
   
 
}

可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第一个字符串b时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时后一个字符串b并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免:

1
2
3
4
5
6
7
8
9
10
11
public
 
static
 
void
 
remove
 
(
 
ArrayList
 
<
 
String
 
>
 
list
 
)
{
for
 
(
 
int
 
i
 
=
 
list
 
.
 
size
 
(
 
)
 
-
 
1
 
;
 
i
 
>=
 
0
 
;
 
i
 
--
 
)
{
String
 
s
 
=
 
list
 
.
 
get
 
(
 
i
 
)
 
;
if
 
(
 
s
 
.
 
equals
 
(
 
"b"
 
)
 
)
{
list
 
.
 
remove
 
(
 
s
 
)
 
;
}
}
}

因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。

接着解释一下实例二的错误原因。错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):

1
2
3
public
 
Iterator
 
<
 
E
 
>
 
iterator
 
(
 
)
 
{
return
 
new
 
Itr
 
(
 
)
 
;
}

这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的next方法:

1
2
3
4
5
6
7
8
9
10
11
public
 
E
 
next
 
(
 
)
 
{
checkForComodification
 
(
 
)
 
;
try
 
{
E
 
next
 
=
 
get
 
(
 
cursor
 
)
 
;
lastRet
 
=
 
cursor
 
++
 
;
return
 
next
 
;
}
 
catch
 
(
 
IndexOutOfBoundsException
 
e
 
)
 
{
checkForComodification
 
(
 
)
 
;
throw
 
new
 
NoSuchElementException
 
(
 
)
 
;
}
}

第一行checkForComodification方法:

1
2
3
4
final
 
void
 
checkForComodification
 
(
 
)
 
{
if
 
(
 
modCount
 
!=
 
expectedModCount
 
)
throw
 
new
 
ConcurrentModificationException
 
(
 
)
 
;
}

这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或for-each的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

1
2
3
4
5
6
7
8
9
10
11
12
public
 
static
 
void
 
remove
 
(
 
ArrayList
 
<
 
String
 
>
 
list
 
)
{
Iterator
 
<
 
String
 
>
 
it
 
=
 
list
 
.
 
iterator
 
(
 
)
 
;
while
 
(
 
it
 
.
 
hasNext
 
(
 
)
 
)
{
String
 
s
 
=
 
it
 
.
 
next
 
(
 
)
 
;
if
 
(
 
s
 
.
 
equals
 
(
 
"b"
 
)
 
)
{
it
 
.
 
remove
 
(
 
)
 
;
}
}
}

转载于:https://www.cnblogs.com/dorothychai/p/6049893.html

你可能感兴趣的文章
JAVA面试常见问题之Redis篇
查看>>
jdk1.8 api 下载
查看>>
getElement的几中属性介绍
查看>>
HTML列表,表格与媒体元素
查看>>
Introduction to my galaxy engine 2: Depth of field
查看>>
设计器 和后台代码的转换 快捷键
查看>>
STL容器之vector
查看>>
数据中心虚拟化技术
查看>>
复习文件操作
查看>>
SQL Server 使用作业设置定时任务之一(转载)
查看>>
第二阶段冲刺-01
查看>>
BZOJ1045 HAOI2008 糖果传递
查看>>
JavaScript 克隆数组
查看>>
eggs
查看>>
oracle 报错ORA-12514: TNS:listener does not currently know of service requested in connec
查看>>
基于grunt构建的前端集成开发环境
查看>>
利用循环播放dataurl的视频来防止锁屏:NoSleep.js
查看>>
python3 生成器与迭代器
查看>>
java编写提升性能的代码
查看>>
Abstract Factory Pattern
查看>>