探究:Java循環刪除List元素的正確姿勢

前言------

上課時老師稍微講了下循環刪除元素只有一種方式可以,其他會錯,但是也就是這麼說一下。。好奇的我,趁這篇博文,整理了3種方式,並分析了爲啥可以和爲啥不可以,整理得很用心,希望自己理解的同時也能幫到路過的你。

 

如同我上篇博文提到,循環變量容器可以用for(),foreach, 迭代器遍歷這三種,那麼是否意味着循環刪除List中的元素也可以有這三種方式呢?想起來貌似可以,畢竟刪除離不開循環鴨。我們就三種方式都來試試

 

目錄

一.for循環刪除List中的元素

二.迭代器刪除List中的元素

三.foreach循環刪除List中的元素

4.乾貨總結


 

一.for循環刪除List中的元素

package Test;

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

import Charactor.String;

public class TestDeleteOfContainer {

	List <String> strings = new ArrayList<>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		TestDeleteOfContainer myTest = new TestDeleteOfContainer();
		myTest.forDelete();

	}
	public void forDelete() {
		
		String temp ;
		//添加100個String對象
		for(int i = 0 ;i < 100 ;i++) {
			
			strings.add(new String("s" + i));
		}
		
		for(int i = 0 ;i <strings.size();i++) {
			
				strings.remove(i);//滿足則刪除
	
			
		}
		
		System.out.println("現在,strings的大小爲:" + strings.size());
	}

運行結果:現在,strings的大小爲:50  

怎麼回事呢,爲啥總共有100個String對象,刪除了一遍後,卻還剩50個呢?應該要只剩下0個而已啊!!

原因:在for循環刪除的過程中,List表的size是隨着刪除不斷改變的。

假如原先List中有1-8個元素,現在remove了箭頭所指的第一個元素,因爲第一個元素被刪了,後面的元素就會一同往前挪一位,原先的2到達了1的位置,原先的3到達了2位置,i++導致箭頭往後移,指向了元素3,其實2纔是本應該被刪除的元素,但是卻被遺漏了,這也就是爲什麼還剩下50個元素沒有被刪除的原因。所以for循環方式不能正確刪除List中的元素

二.迭代器刪除List中的元素

package Test;

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

import Charactor.String;

public class TestDeleteOfContainer {

	List <String> strings = new ArrayList<>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		TestDeleteOfContainer myTest = new TestDeleteOfContainer();
		myTest.iteratorDelete() ;

	}
	
	public void iteratorDelete() {
		
		String temp;
		//添加100個對象
		for(int i = 0 ;i < 100 ;i++) {
			
			strings.add(new String("S"+ i));
		}
		
		Iterator it = strings.iterator();
		while(it.hasNext()) {
			
			temp = (String)it.next();
			if(temp.equals("S8"){
			  it.remove();
                          } 
			
		}
		System.out.println("現在,strings的大小爲:" + strings.size());
	}
}

 

運行結果:現在,strings的大小爲:99。

上述運行結果說明成功刪除。成功原因下面分析,暫且不提。

試想:寫成下面這種形式呢?

package Test;

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

import Charactor.String;

public class TestDeleteOfContainer {

	List <String> strings = new ArrayList<>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		TestDeleteOfContainer myTest = new TestDeleteOfContainer();
		myTest.iteratorDelete() ;

	}
	
	public void iteratorDelete() {
		
		String temp;
		//添加100個對象
		for(int i = 0 ;i < 100 ;i++) {
			
			strings.add(new String("S"+ i));
		}
		

		for(Iterator it = strings.iterator(); it.hasNext();) {
			
			
			temp = (String)it.next();
			if(temp.equals("S8")) {
			   strings.remove(temp);
			}
		}
		System.out.println("現在,strings的大小爲:" + strings.size());
	}
}

運行結果:出現了java.util.ConcurrentModificationException  異常,這是什麼原因呢?這如果要解釋的話,必須要涉及Java中List的源碼了。不過我先給大家講一講大致的原理:

List中有一個modCount的變量,是一個計數變量,計數迭代循環過程中調用了多少次List.add()和List.remove()。

但是List中還有一個expectedModCount的變量,這個變量初始值和modCount一致。每次迭代調用it.next()和it.remove()時都會先判斷二者是否相同,如果不相同就會拋出並行修改的異常。爲了在修改中不拋出異常,迭代器中有個remove()方法,這個方法本質上也是調用List的remove()方法,雖然每次remove後,modCount的值會+ 1,但是我無只要同步修改expectedModCount 的值就可以了,使得二者永遠等同就 可以啦。

因此改成iI.remove就可以啦!!!

說了這麼多,是不是沒看懂?我放源碼啦,自己領悟下

AbsrtactList中iteraor方法,它返回一個內部類,這個類實現了iterator接口,代碼如下:

public Iterator<E> iterator() {
    return new Itr();
}
private class Itr implements Iterator<E> {
    int cursor = 0;
 
    int lastRet = -1;
 
    int expectedModCount = modCount;
 
    public boolean hasNext() {
        return cursor != size();
    }
 
    public E next() {
        checkForComodification();
        try {
            E next = get(cursor);
            lastRet = cursor++;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }
 
    public void remove() {
        if (lastRet == -1)
            throw new IllegalStateException();
        checkForComodification();
 
        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            // 同步修改expectedModCount 的值
            expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
 
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
    }


是不是還沒看明白呢?那就總結個乾貨,直接記起來,迭代循環刪除元素中,只能調用迭代器的remove方法,不能調用List的

remove方法,對上面例子來說,就是隻能用it.remove(temp),不能用string.remove(temp)~~~~~

 

三.foreach循環刪除List中的元素

package Test;

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

import Charactor.String;

public class TestDeleteOfContainer {

	List <String> strings = new ArrayList<>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		TestDeleteOfContainer myTest = new TestDeleteOfContainer();
		myTest.foreachDelete();

	}
	
	public void foreachDelete() {
		

		//添加100個String對象
		for(int i = 0 ;i < 100 ;i++) {
			
			strings.add(new String("s" + i));
		}
		
		for(String temp: strings) {
			
			if(temp.equals("S8")) {
			   strings.remove(temp);
			}
		}
		
		System.out.println("現在,strings的大小爲:" + strings.size());
		
	}
}

運行結果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at Test.TestDeleteOfContainer.foreachDelete(TestDeleteOfContainer.java:29)
    at Test.TestDeleteOfContainer.main(TestDeleteOfContainer.java:16)
 

也出現了java.util.ConcurrentModificationException  異常,這有是什麼原因呢?其實這個原因和上面的一樣,都是調用了List中的remove方法了,造成了並行修改的錯誤。因此foreach修改也不行。

四.乾貨總結

只能用迭代循環,且過程中只能調用迭代器的remove方法。

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章