方针:list中有0到39共40个元素,删去其中索引是10、20、30的元素
计划一:运用一般for循环早年往后遍历再删去
初始化List列表
List<String> list = new ArrayList<>();
for (int i = 0; i < 40; i++) {
list.add("element" + i);
}
首先当咱们删去第10位元素时,List会将后边的元素向前补位,之后再查第10位元素就会输出第11位元素
for (int i = 0; i < list.size(); i++) {
if (i == 10) {
list.remove(i);
}
}
System.out.println(list.get(10));
输出:element11
那么删去了一个元素今后,后边需求删去的元素方位就向前提1位
/**
* 假如清晰需求删去元素的方位
* 那么能够这样,每删去一个元素后就把下一个要删去元素的方位减1
* 留意这么做有个需求留意的点,那便是每次删去完节点后遍历指针i需求减一,这样在删去两个临近节点时才不会出现问题
* 比如要删去10和11
*/
for (int i = 0; i < list.size(); i++) {
if (i == 10) {
list.remove(i);
i--;
}
if (i == 19) {
list.remove(i);
i--;
}
if (i == 28) {
list.remove(i);
i--;
}
}
System.out.println(list.contains("element10"));
System.out.println(list.contains("element20"));
System.out.println(list.contains("element30"));
输出:
false
false
false
当然咱们能够用一个数组或列表从小到大存储需求删去的方位,然后再for循环中进行运算和取值
计划二:运用一般for循环从后往前遍历再删去
从后向前遍历的优点是咱们不需求再像计划一一样每删去一个元素都需求去考虑后边元素向前补位的问题
for (int i = list.size() - 1; i >= 0; i--) {
if (i == 30) {
list.remove(i);
}
if (i == 20) {
list.remove(i);
}
if (i == 10) {
list.remove(i);
}
}
System.out.println(list.contains("element10"));
System.out.println(list.contains("element20"));
System.out.println(list.contains("element30"));
输出:
false
false
false
从后向前,即便后边进行元素进行向前补位操作也不会影响前面需求删去的元素
这里也能够用一个数组或列表存储需求删去的元素,从大到小摆放,取出一个删去一个
计划三:运用迭代器删去
Iterator<String> iterator = list.iterator();
int i = 0;
while (iterator.hasNext()) {
String next = iterator.next();
if (i == 10) {
iterator.remove();
}
i++;
}
System.out.println(list.get(10));
System.out.println(list.contains("element10"));
输出:
element11
false
在迭代器中维护一个数字i标识遍历的方位
假如咱们在迭代器中继续删去另外20和30方位元素
Iterator<String> iterator = list.iterator();
int i = 0;
while (iterator.hasNext()) {
String next = iterator.next();
if (i == 10) {
iterator.remove();
}
if (i == 20) {
iterator.remove();
}
if (i == 30) {
iterator.remove();
}
i++;
}
System.out.println(list.get(10));
System.out.println(list.contains("element10"));
System.out.println(list.get(20));
System.out.println(list.contains("element20"));
System.out.println(list.get(30));
System.out.println(list.contains("element30"));
输出:
element11
false
element22
false
element33
false
首先咱们在迭代过程中指定的是删去10、20、30三个方位的元素,能够看到输出contains时都是false表示正确删去,可是终究输出列表的值发现对应索引方位现已进行了补位。
咱们debug剖析一下为什么
先简单介绍一下Iterator和Iterable
- Iterable是一个迭代接口,完成了这个接口代表该类能够迭代
能够看到咱们的调集Collection接口便是它的子类

// 回来一个完成了Iterator接口的目标,咱们也是用这个目标去进行迭代
Iterator<T> iterator();
- Iterator,它首要有三个办法:
// 回来是否还有下一个元素
boolean hasNext();
// 回来下一个元素
E next();
// 删去该元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
每个不同的调集类都会有不同的Iterator接口完成,在ArrayList中运用了一个内部类来完成
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;
Itr() {}
}
咱们通过list.iterator()拿到的便是这个内部类的目标实例,这个类中有两个字段cursor和lastRet,这两个字段便是咱们能在迭代器中正确删去对应方位的元素的要害。
有关expectedModCount和modCount的问题后边会补充,咱们先不用重视
cursor初始化是0 lastRet初始化是-1
剖析next和remove办法的源码
/**
* 能够先不重视这两个Exception
* 每次调用next() cursor都会+1 而lastRet就会变成之前cursor的值
* cursor初始化是0
* lastRet初始化是-1
* 调用一次今后 cursor是1 lastRet变成0
**/
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 {
// 调用本身的remove办法
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
要害是这个remove办法对cursor和lastRet的修改
假如正在删去第10个元素
执行remove办法前cursor应该是11,lastRet是10
执行了今后lastRet变成了-1,cursor变成了10
下次执行next()办法回来的元素其实仍是elementData[10]
也便是List补位后正确的下一个元素,cursor变成了11,lastRet是10
总结:运用迭代器遍历时ArrayList会用lastRet和cursor两个变量来维护当前遍历的元素索引和下一次需求遍历元素的索引,通过这两个变量就能够完成迭代中正确的删去某个方位的元素。