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


176

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

왜 '다음 코드 샘플에서는 이해가 안 List.remove의 main방법은 발생 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);
            }
        }
    }
}

3
해당 목록을 반복하면서 목록에서 요소를 제거하는 유일한 안전한 방법은을 사용하는 것 Iterator#remove()입니다. 왜 이런 식으로하고 있습니까?
— 매트 볼

@ MatBall : 나는 여기에 이유가 무엇인지 보려고했습니다. 왜냐하면 두 메소드 모두에서 동일한 "enhanced for loop"이기 때문에 하나는 던지고 ConcurrentModificationException다른 하나는 그렇지 않습니다.
— Bhesh Gurung

제거하는 요소에 차이가 있습니다.이 방법에서 '중간 요소'를 제거하십시오. 메인에서는 마지막을 제거합니다. 숫자를 바꾸면 메소드에서 예외가 발생합니다. 그래도 왜 그런지 잘 모르겠습니다.
— 벤 반 Gompel

루프에서 항목을 제거한 후에도 루프가 존재하지 않는 위치를 반복 할 때 비슷한 문제가 발생했습니다. return;루프 에를 추가하여 간단히 고쳤습니다.
— frank17

java8 Android에서 마지막 요소 이외의 요소를 제거하면 ConcurrentModificationException이 호출됩니다. 따라서 귀하의 경우 remove 함수는 이전에 관찰 된 것과 반대되는 예외를 얻습니다.
— gonglong

답변:


262

이유는 다음과 같습니다. 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되지 않습니다 .


5
@ 푸시 : 질문에 실제로 묻는 것에 대답하는 것처럼 보이는 대답 만 설명이 좋습니다. 이 답변을 수락하고 +1합니다. 감사.
— Bhesh Gurung

42

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);
    }
}

14

항목을 제거 할 때 전달 / 반복자 방법이 작동하지 않습니다. 오류없이 요소를 제거 할 수 있지만 제거 된 항목에 액세스하려고하면 런타임 오류가 발생합니다. 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);
    }
}

훌륭한 아이디어!
— dobrivoje

7

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

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

JavaDocs :

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

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

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


3
OP는 분명히 루프 중 하나가 예외를 throw하지 않으며 심문이 그 이유라고 지적했습니다.
— madth3

'심문 관'이란 무엇입니까?
— 부샨

4

나는 같은 문제가 있었지만 반복 된 목록에 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()목록 감소 / 증가의 목록 크기에서 무언가를 제거 / 추가 할 때 조건 은 결코 당신을 속이지 않을 것입니다.

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


이것은 사실이 아닙니다! 증거 :이 스 니펫을 실행하여 결과를 확인하십시오. public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ( "Code Complete"); listOfBooks.add ( "Code 22"); listOfBooks.add ( "22 Effective"); listOfBooks.add ( "Netbeans 33"); System.err.println ( "삭제하기 전에 :"+ listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ( "22")) {listOfBooks.remove (index); }} System.err.println ( "삭제 후 :"+ listOfBooks); }
— dobrivoje

1

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);
            }
        }
    }
}

0

이것은 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);
            }
        }
    }
}

~ %


오타가 유감입니다. Java 1.6에서
— 제대로

흠 ... 당신은 다른 구현이있을 수 있습니다. 그러나 사양에 따르면 IMO라고해야합니다. @Pushy의 답변을보십시오.
— Bhesh Gurung

불행히도, id는 Java 1.8에 있지 않습니다
— dobrivoje

0

제 경우에는 다음과 같이했습니다.

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

Iterator for each를 변경 for loop하여 해결하십시오.

그리고 그 이유는 다음과 같습니다.

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

-참조 Java 문서.


-1

코드 맨을 확인하십시오 ....

주요 방법에서는 존재하지 않는 4 번째 요소를 제거하려고하므로 오류가 발생합니다. remove () 메소드에서 세 번째 요소를 제거하려고하므로 오류가 없습니다.


당신은 오해 : 숫자 2와 3목록의 색인이 아니라 요소입니다. 두 가지 제거 로직 모두 equals요소의 색인이 아닌 목록 요소를 검사합니다 . 또한 인덱스와 관련이 있으면 IndexOutOfBoundsException그렇지 않습니다 ConcurrentModificationException.
— Malte Hartwig
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.