Javaプログラミング:List要素を削除する3つの正しい方法


リストの要素を削除すると、次の2つの問題が発生します。

  • 要素を削除するとリストの要素数が変化します.
  • 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 list = new ArrayList();
    		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 list){
    		for (String item : list) {
    			System.out.println(" :" + item);
    		}
    	}
    
    	/*
    	 *  
    	 */
    	public static void remove11(List 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 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 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 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 list, String target){
    		for(String item : list){
    			if(target.equals(item)){
    				list.remove(item);
    			}
    		}
    		print(list);
    	}
    	
    	/*
    	 *  
    	 */
    	public static void remove22(ArrayList list, String target) {
    		final CopyOnWriteArrayList cowList = new CopyOnWriteArrayList(list);
    		for (String item : cowList) {
    			if (item.equals(target)) {
    				cowList.remove(item);
    			}
    		}
    		print(cowList);
    	}
    	
    	/*
    	 *  
    	 */
    	public static void remove31(List list, String target){
    		Iterator iter = list.iterator();
    		while (iter.hasNext()) {
    			String item = iter.next();
    			if (item.equals(target)) {
    				list.remove(item);
    			}
    		}
    		print(list);
    	}
    	/*
    	 *  
    	 */
    	public static void remove32(List list, String target){
    		Iterator iter = list.iterator();
    		while (iter.hasNext()) {
    			String item = iter.next();
    			if (item.equals(target)) {
    				iter.remove();
    			}
    		}
    		print(list);
    	}
    
    }
    
    

    remove 11メソッドを実行すると、次のエラーが発生します。


    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();がリストのサイズを早めに取得したため、forループで2つの要素が削除され、配列境界の問題が発生した.

    remove 12メソッドを実行すると、次のエラーが発生します。


    要素値:beijing要素値:shanghai要素値:guangzhou要素値:shenzhen要素値:hangzhou
    文字列"shanghai"は削除する、この方法は配列の境界を越えた問題を解決したが、完全にデータを削除する問題を解決しなかった.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;
        }
    
    

    要素を削除するときに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
        }
    

    コードにより、リスト削除要素の論理は、ターゲット要素の後の要素をインデックス位置に移動し、最後の要素をnullに設定し、size-1であることが分かった.これは、2番目の「shanghai」が削除されていない理由を説明します.

    remove 13メソッドを実行します。正しいです。


    要素値:beijing要素値:guangzhou要素値:shenzhen要素値:hangzhou

    remove 14メソッドを実行します。正しいです。


    要素値:beijing要素値:guangzhou要素値:shenzhen要素値:hangzhou
    ではremove 13とremove 14の違いは何でしょうか.答えは違いませんが、remove 11とremove 12には違いがあり、remove 12ではfor(int i = 0; i < list.size(); i++)の実行ごとにsize値が計算され、消費性能が比較されます.

    remove 21メソッドを実行すると、次のエラーが発生します。

    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)
    
    
    java.util.ConcurrentModificationException異常が発生しました.foreachの書き方は実際には正しいIterable,hasNext,nextメソッドの略です.そこで,List.iterator()から解析に着手し,Itr反復器オブジェクトを返すiterator()法を追跡した.
    	public Iterator iterator() {
    		return new Itr();
    	}
    

    Itrクラス定義コード:
        private class Itr implements Iterator {
            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();
            }
        }
    
    

    コードによりItrはArrayListで定義されたプライベート内部クラスであり,next,removeメソッドではいずれもcheckForComodificationメソッドが呼び出され,modCount != expectedModCountが等しいか否かを判断し,等しくなければConcurrentModificationException異常を放出する役割を果たす.removeメソッドが正常に実行されるたびに、expectedModCount = modCountを実行し、2つの値が等しいことを保証すると、問題は基本的に明らかになり、foreachサイクルでlist.remove(item);を実行し、listオブジェクトのmodCount値を変更したが、listオブジェクトの反復器のexpectedModCount値は変更されなかったため、ConcurrentModificationException異常が投げ出された.

    remove 22メソッドを実行します。正しいです。


    要素値:beijing要素値:guangzhou要素値:shenzhen要素値:hangzhouはCopyOnWriteArrayListによってListの同時問題を解決した.

    remove 31メソッドを実行します。次のエラーが発生しました。


    Exception in thread “main” java.util.ConcurrentModificationException at java.util.ArrayList I t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 859 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList Itr.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)
    実行remove 21による異常と一致し、問題発生の原因も一致する.

    remove 32メソッドを実行します。正しいです。


    要素値:beijing要素値:guangzhou要素値:shenzhen要素値:hangzhou