在我们的实际开发中,list绝对是用的最多的集合类,然而对于很多人来说,用的最多的出错也是最多的。尤其是list的增删操作。
public class Test {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(1);
list.add(1);
list.add(3);
list.add(4);
list.add(1);
list.add(5);
for(int i = 0;i
运行结果:
1
3
4
1
当前list:[1, 3, 4, 5]
list里一共有6个元素,遍历加删除结果就4个,而且 元素值5还不见了,这种bug对于新手来说比抛异常难受多了呢(我明明有数据,怎么展示出来就没了,我又没删除5,最终的list怎么还有1!!!)。
这个时候有“聪明的小伙伴”就想到了6个元素,我可以这样改:
List list = new ArrayList<>();
list.add(1);
list.add(1);
list.add(3);
list.add(4);
list.add(1);
list.add(5);
for(int i = 0;i<6;i++){
System.out.println(list.get(i));
if(1==list.get(i)){
list.remove(i);
}
}
System.out.println("当前list:"+list.toString());
结果:
1
3
4
1
Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 4, Size: 4
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.Test.main(Test.java:29)
Process finished with exit code 1
终于报异常了呢,好开心。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
关于 modCount,是这样描述的:
此列表在结构上被修改的次数。结构修改是指改变列表大小的修改,或者以其他方式扰乱列表,使得正在进行的迭代可能会产生不正确的结果。此字段由迭代器和listIterator方法返回的迭代器和列表迭代器实现使用。如果此字段的值意外更改,迭代器(或列表迭代器)将抛出ConcurrentModificationException,以响应下一个、删除、上一个、设置或添加操作。这提供了快速失败的行为,而不是在迭代过程中面对并发修改时的非确定性行为。子类是否使用此字段是可选的。如果一个子类希望提供快速失败迭代器(和列表迭代器),那么它只需在其add(int,E)和remove(int)方法(以及它覆盖的任何其他导致列表结构修改的方法)中递增此字段。单次调用add(int,E)或remove(int)必须向此字段添加不超过一个,否则迭代器(和列表迭代器)将抛出虚假的ConcurrentModificationExceptions。如果实现不希望提供快速失败迭代器,则可以忽略此字段。
很明显一边遍历一边变更增删元素,数组的长度都会改变。那一边遍历一边增删必然会出现很多问题。
List list = new ArrayList<>();
list.add(1);
list.add(1);
list.add(3);
list.add(4);
list.add(1);
list.add(5);
Iterator iterator = list.listIterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
System.out.println(value);
if (value.equals(1)) {//1是要删除的元素
iterator.remove();
}
}
System.out.println("当前list是"+list.toString());
}
结果
1
1
3
4
1
5
当前list是[3, 4, 5]
利用Iterator进行操作(不熟悉迭代器的小伙伴这时候就需要学习了)来避免list方法中改变数组长度。
List list = new ArrayList<>();
List list1 = new ArrayList<>();
list.add(1);
list.add(1);
list.add(3);
list.add(4);
list.add(1);
list.add(5);
for(int i = 0;i
结果:
1
1
3
4
1
5
当前list1:[3, 4, 5]
Disconnected from the target VM, address: '127.0.0.1:56792', transport: 'socket'
Process finished with exit code 0
利用另一个list做容器,这样就避免了同一个list一边遍历一边删除。
首先list不可以一边遍历一边增删,看到list就多长个心眼:其他集合类有没有可能也有这种情况!!!
其次,正确的实现方式不仅仅是我展示的两个 ,stream也可以用等等。
最后,从实战角度来说,我们操作的list远比示例的复杂,多数情况是list中所有:一个对象转换成另一个对象,符合要求的转,不符合要求的不要,Iterator“好看但往往不符合实际使用”,利用另一个list做容器的方式才是我们代码里最常出现的方式。
希望这篇文章对你有帮助。