[java] 이 예제에서 java.util.ConcurrentModificationException이 발생하지 않는 이유는 무엇입니까?

참고 : Iterator#remove()방법을 알고 있습니다.

왜 ‘다음 코드 샘플에서는 이해가 안 List.removemain방법은 발생 ConcurrentModificationException하지만, 하지remove방법.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}



답변

이유는 다음과 같습니다. Javadoc에서 말하는 것처럼 :

이 클래스의 이터레이터와 listIterator 메소드에 의해 리턴 된 이터레이터는 빠르다 : 이터레이터가 작성된 후 언제라도리스트가 구조적으로 수정되면, 이터레이터 자체의 remove 또는 add 메소드를 제외하고 어떤 식 으로든 이터레이터는 ConcurrentModificationException을 발생시킨다.

이 검사는 next()스택 트레이스에서 볼 수 있듯이 반복자 의 메소드 에서 수행됩니다 . 그러나 우리는 true로 전달 된 next()경우에만 메소드에 도달 할 것 hasNext()입니다. 이는 경계가 충족되는지 확인하기 위해 각각에 의해 호출됩니다. remove 메소드에서 hasNext()다른 요소를 리턴해야하는지 점검 할 때 두 개의 요소를 리턴했으며 이제 하나의 요소가 제거 된 후 목록에는 두 개의 요소 만 포함됩니다. 그래서 모든 것이 복숭아이며 우리는 반복으로 끝납니다. next()호출되지 않은 메소드 에서 수행되므로 동시 수정 점검이 수행되지 않습니다 .

다음으로 두 번째 루프로 넘어갑니다. 두 번째 숫자를 제거한 후 hasNext 메소드는 더 많은 값을 반환 할 수 있는지 다시 확인합니다. 이미 두 개의 값을 반환했지만 이제는 목록에 하나만 포함됩니다. 그러나 여기 코드는 다음과 같습니다

public boolean hasNext() {
        return cursor != size();
}

1! = 2, 그래서 우리는 next()방법 을 계속합니다. 이제 누군가 누군가 목록을 엉망으로 만들고 예외를 발생시킵니다.

희망이 당신의 질문을 해결합니다.

요약

List.remove()ConcurrentModificationException목록에서 두 번째 마지막 요소를 제거해도 throw되지 않습니다 .


답변

Collection해당되는 경우 (컬렉션 자체가 아닌) 사본에서 무언가를 제거하기 위해 처리하는 한 가지 방법 . Clone원본 컬렉션을 통해 Constructor.

이 예외는 그러한 수정이 허용되지 않을 때 객체의 동시 수정을 감지 한 메소드에 의해 발생할 수 있습니다.

특정 사례의 경우, 먼저 final선언을지나 목록을 수정하려고 한다고 생각하지 않는 방법 이라고 생각 합니다.

private static final List<Integer> integerList;

또한 원래 목록 대신 사본을 수정하십시오.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}


답변

항목을 제거 할 때 전달 / 반복자 방법이 작동하지 않습니다. 오류없이 요소를 제거 할 수 있지만 제거 된 항목에 액세스하려고하면 런타임 오류가 발생합니다. pushy가 보여 주듯이 ConcurrentModificationException이 발생하므로 반복자를 사용할 수 없으므로 대신 일반 for 루프를 사용하지만 거꾸로 건너 뜁니다.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

해결책 :

목록 요소를 제거하려는 경우 배열을 역순으로 순회하십시오. 목록을 거꾸로 이동하면 제거 된 항목을 방문하지 않아도되므로 예외가 제거됩니다.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}


답변

이 스 니펫은 항상 ConcurrentModificationException을 발생시킵니다.

규칙은 “이터레이터 (for-each 루프를 사용할 때 발생)를 사용하여 반복하는 동안 목록에서 요소를 추가 또는 제거 할 수 없습니다”입니다.

JavaDocs :

이 클래스의 이터레이터와 listIterator 메소드에 의해 리턴 된 이터레이터는 빠르다 : 이터레이터가 작성된 후 언제라도리스트가 구조적으로 수정되면, 이터레이터 자체의 remove 또는 add 메소드를 제외하고 어떤 식 으로든 이터레이터는 ConcurrentModificationException을 발생시킨다.

따라서 목록 (또는 일반적으로 모든 컬렉션)을 수정하려면 수정자를 알고 있으므로 이터레이터를 사용하면 올바르게 처리됩니다.

도움이 되었기를 바랍니다.


답변

나는 같은 문제가 있었지만 반복 된 목록에 en 요소를 추가하는 경우. 내가 이렇게 만들었 어

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

이제 목록에 반복자를 만들지 않고 “수동으로”반복하므로 모든 것이 잘됩니다. 그리고 i < integerList.size()목록 감소 / 증가의 목록 크기에서 무언가를 제거 / 추가 할 때 조건 은 결코 당신을 속이지 않을 것입니다.

그것이 도움이되기를 바랍니다.


답변

COW (Copy-On-Write) 컬렉션을 사용하면 작동합니다. 그러나 list.iterator ()를 사용하면 반환 된 Iterator는 다른 스레드가 컬렉션을 수정하더라도 (아래와 같이) list.iterator ()를 호출했을 때와 같이 항상 요소 컬렉션을 참조합니다. copy-on-write-based Iterator 또는 ListIterator (예 : add, set 또는 remove)에서 호출 된 변경 메소드는 UnsupportedOperationException을 발생시킵니다.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}


답변

이것은 Java 1.6에서 잘 실행됩니다.

~ % javac RemoveListElementDemo.java
~ % java RemoveListElementDemo
~ % 고양이 RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~ %