面試題:刪除列表中等於bb的元素
先看測試案例:
import java.util.ArrayList;
public class ArrayListRemove {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("bb");
list.add("bb");
list.add("ccc");
list.add("ccc");
list.add("ccc");
remove(list);
for (String s : list) {
System.out.println("element : " + s);
}
}
public static void remove(ArrayList<String> list) {
// TODO:
}
}
錯誤寫法示例一:
public static void remove(ArrayList<String> list) {
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.equals("bb")) {
list.remove(s);
}
}
}
這種最普通的循環寫法執行後會發現有一個“bb”的字符串沒有刪掉。
錯誤寫法示例二:
public static void remove(ArrayList<String> list) {
for (String s : list) {
if (s.equals("bb")) {
list.remove(s);
}
}
}
這種for each寫法會發現報出著名的併發修改異常Java.util.ConcurrentModificationException。
要分析產生上述錯誤現象的原因唯有看看JDK的ArrayList的源碼,先看下ArrayList中的remove方法(注意ArrayList中的remove有兩個同名方法,只是輸入參數不同,這裏看的是輸入參數是Object的remove方法)是怎麼實現的:
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路徑下最終調用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; // Let gc do its work
}
可以看到會執行System.arraycopy方法,導致刪除元素時涉及到數組元素的移動。針對錯誤寫法一,在遍歷第二個元素字符串bb時因爲符合刪除條件,所以將該元素從數組中刪除,並且將後一個元素移動(也是字符串bb)至當前位置,導致下一次循環遍歷時後一個字符串bb並沒有遍歷到,所以無法刪除。針對這種情況可以倒序刪除的方式來避免。
解決辦法如下:
public static void remove(ArrayList<String> list){
for(int i=list.size()-1;i>=0;i--){
String s=list.get(i);
if(s.equals("bb")){
list.remove(s);
}
}
}
因爲數組倒序遍歷時即使發生元素刪除也不影響後序元素遍歷。
而錯誤二產生的原因卻是for each增強for循環寫法是對實際的Iterator、hasNext、next方法的簡寫,問題同樣處在上文的fastRemove中,可以看到第一行把modCount變量的值加1,但在ArrayList返回的迭代器(該代碼在其父類AbstractList中)
public Iterator<E> iterator() {
return new Itr();
}
這裏返回的是AbstractList類內部的迭代器實現private class Itr implements Iterator ,看這個類的next方法:
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
第一行checkForComodification方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
總結:
錯誤原因都是ArrayList集合中remove方法底層的源碼中有一個fastRemove(index)方法,然後會有一個modCount++的操作,然後在ArratList內部的迭代器中有一個checkForComodification操作,也就是檢查modCount是否改變,如果改變了,就拋出併發修改錯誤。 同樣的在For each增強for循環中,也是利用了ArrayList自身的Iterator迭代器,也是會出現這樣的錯誤。
對於一般的for遍歷,可能並沒有刪除要修改的數,可以採用倒序刪除的寫法改正這個錯誤。 對於增強for循環中的遍歷,會拋出併發修改異常,使用Iterator自己的remove方法。
從今天開始我增加分類Java面試題,我轉發記錄學習得到的知識點,大家一起學習,一起進步!加油