您的位置:首页 > 编程语言 > Java开发

Java编程:删除 List 元素的三种正确方法

2017-08-25 16:07 597 查看


删除 List 中的元素会产生两个问题:

删除元素后 List 的元素数量会发生变化;
对 List 进行删除操作可能会产生并发问题;

我们通过代码示例演示正确的删除逻辑
package com.ips.list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ArrayListRemove {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("beijing");
list.add("shanghai");
list.add("shanghai");
list.add("guangzhou");
list.add("shenzhen");
list.add("hangzhou");
remove11(list, "shanghai");

}

private static void print(List<String> list){
for (String item : list) {
System.out.println("元素值:" + item);
}
}

/*
* 错误
*/
public static void remove11(List<String> list, String target){
int size = list.size();
for(int i = 0; i < size; i++){
String item = list.get(i);
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}
/*
* 错误
*/
public static void remove12(List<String> list, String target){
for(int i = 0; i < list.size(); i++){
String item = list.get(i);
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}
/*
* 错误
*/
public static void remove13(List<String> list, String target){
int size = list.size();
for(int i = size - 1; i >= 0; i--){
String item = list.get(i);
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}
/*
* 正确
*/
public static void remove14(List<String> list, String target){
for(int i = list.size() - 1; i >= 0; i--){
String item = list.get(i);
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}

/*
* 错误
*/
public static void remove21(List<String> list, String target){
for(String item : list){
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}

/*
* 正确
*/
public static void remove22(ArrayList<String> list, String target) {
final CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<String>(list);
for (String item : cowList) {
if (item.equals(target)) {
178d9
cowList.remove(item);
}
}
print(cowList);
}

/*
* 错误
*/
public static void remove31(List<String> list, String target){
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
if (item.equals(target)) {
list.remove(item);
}
}
print(list);
}
/*
* 正确
*/
public static void remove32(List<String> list, String target){
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
if (item.equals(target)) {
iter.remove();
}
}
print(list);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131


执行 remove11 方法,出现如下错误:

Exception in thread “main” Java.lang.IndexOutOfBoundsException: Index: 5, Size: 5 

at java.util.ArrayList.rangeCheck(ArrayList.java:635) 

at java.util.ArrayList.get(ArrayList.java:411) 

at com.ips.list.ArrayListRemove.remove11(ArrayListRemove.java:33) 

at com.ips.list.ArrayListRemove.main(ArrayListRemove.java:17)

由于
int size = list.size();
提前获取了 List 的大小,for 循环中删除了两个元素,导致出现数组越界问题。


执行 remove12 方法,出现如下错误:

元素值:beijing 

元素值:shanghai 

元素值:guangzhou 

元素值:shenzhen 

元素值:hangzhou

字符串“shanghai”没有被删除,该方法解决了数组越界问题,但没有解决彻底删除数据的问题,原因是这样的,跟踪 ArrayList.remove(Object 0) 方法:
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

删除元素时执行 else 逻辑,调用了 fastRemove(index) 方法:
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; // clear to let GC do its work
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

通过代码我们发现:List 删除元素的逻辑是将目标元素之后的元素往前移一个索引位置,最后一个元素置为 null,同时 size - 1;这也就解释了为什么第二个“shanghai”没有被删除。


执行 remove13 方法,正确:

元素值:beijing 

元素值:guangzhou 

元素值:shenzhen 

元素值:hangzhou


执行 remove14 方法,正确:

元素值:beijing 

元素值:guangzhou 

元素值:shenzhen 

元素值:hangzhou

那么 remove13 与 remove14 有什么区别呢?答案是没有区别,但是 remove11 与 remove12 是有区别的,remove12 中每次
for(int i = 0; i < list.size(); i++)
执行都会计算 size 值,比较耗性能。


执行 remove21 方法,出现如下错误:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.ips.list.ArrayListRemove.remove21(ArrayListRemove.java:82)
at com.ips.list.ArrayListRemove.main(ArrayListRemove.java:17)
1
2
3
4
5
6
1
2
3
4
5
6

产生
java.util.ConcurrentModificationException
异常。foreach 写法实际上是对的 Iterable、hasNext、next方法的简写。因此我们从
List.iterator()
着手分析,跟踪
iterator()
方法,该方法返回了
Itr 迭代器对象。
public Iterator<E> iterator() {
return new Itr();
}
1
2
3
1
2
3

Itr 类定义代码:
private class Itr implements Iterator<E> {
int cursor;       // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

public boolean hasNext() {
return cursor != size;
}

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

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

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

通过代码我们发现 Itr 是 ArrayList 中定义的一个私有内部类,在 next、remove方法中都会调用 checkForComodification 方法,该方法的作用是判断 
modCount != expectedModCount
是否相等,如果不相等则抛出
ConcurrentModificationException
异常。每次正常执行
remove 方法后,都会对执行
expectedModCount = modCount
赋值,保证两个值相等,那么问题基本上已经清晰了,在 foreach 循环中执行 
list.remove(item);
,对
list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了
ConcurrentModificationException
异常。


执行 remove22 方法,正确:

元素值:beijing 

元素值:guangzhou 

元素值:shenzhen 

元素值:hangzhou 

通过 
CopyOnWriteArrayList
 解决了 List的并发问题。


执行 remove31 方法,出现如下错误:

Exception in thread “main” java.util.ConcurrentModificationException 

at java.util.ArrayListItr.checkForComodification(ArrayList.java:859)atjava.util.ArrayListItr.next(ArrayList.java:831) 

at com.ips.list.ArrayListRemove.remove31(ArrayListRemove.java:109) 

at com.ips.list.ArrayListRemove.main(ArrayListRemove.java:17)

与执行 remove21 产生的异常一致,问题产生的原因也一致。


执行 remove32 方法,正确:

元素值:beijing 

元素值:guangzhou 

元素值:shenzhen 

元素值:hangzhou
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java arraylist