ArrayList에서 요소를 반복하고 제거 할 때 java.util.ConcurrentModificationException을 피하는 방법


203

반복하고 싶은 ArrayList가 있습니다. 그것을 반복하면서 동시에 요소를 제거해야합니다. 분명히 이것은을 던집니다 java.util.ConcurrentModificationException.

이 문제를 처리하는 가장 좋은 방법은 무엇입니까? 먼저 목록을 복제해야합니까?

루프 자체가 아닌 코드의 다른 부분에서 요소를 제거합니다.

내 코드는 다음과 같습니다

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomething호출 할 수 있습니다 Test.removeA();


답변:


325

두 가지 옵션 :

  • 루프 내에서 해당 목록에 추가 하여 제거하려는 값 목록을 작성한 다음 originalList.removeAll(valuesToRemove)끝에서 호출 하십시오.
  • remove()반복자 자체 에서 메소드를 사용하십시오 . 이는 향상된 for 루프를 사용할 수 없음을 의미합니다.

두 번째 옵션의 예로, 목록에서 길이가 5보다 큰 문자열을 제거하십시오.

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}

2
루프 자체가 아닌 코드의 다른 부분에서 요소를 제거한다고 언급 했어야합니다.
RoflcoptrException

@Roflcoptr : 두 비트의 코드가 어떻게 상호 작용하는지 보지 않으면 대답하기가 어렵습니다. 기본적으로는 할 수 없습니다. 목록을 복제하는 것이 어떻게되는지 보지 않고 목록을 먼저 복제하는 것이 도움이 될지 확실하지 않습니다. 질문에 더 자세한 정보를 줄 수 있습니까?
Jon Skeet

나는 목록을 복제하는 것이 도움이된다는 것을 알고 있지만 그것이 좋은 접근 방법인지는 모른다. 하지만 코드를 더 추가하겠습니다.
RoflcoptrException

2
이 솔루션은 또한 java.util.ConcurrentModificationException을 발생 시킵니다 ( stackoverflow.com/a/18448699/2914140 참조) .
CoolMind

1
@ CoolMind : 다중 스레드가 없으면이 코드가 좋습니다.
Jon Skeet

17

ArrayList의 JavaDoc에서

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


6
질문에 대한 답은 어디에 있습니까?
Adelin

반복자의 자체 제거 또는 추가 방법을 제외하고
Varun Achar

14

고급 "for loop"의 목록에서 값을 제거하려고합니다. 코드에서 수행 한 트릭을 적용하더라도 불가능합니다. 더 좋은 방법은 다른 조언과 같이 반복자 수준을 코딩하는 것입니다.

사람들이 전통적인 for 루프 접근법을 어떻게 제안하지 않았는지 궁금합니다.

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

이것은 잘 작동합니다.


2
이것은 정확하지 않습니다 !!! 요소를 제거하면 다음 요소가 그 위치를 차지하고 i가 증가하는 동안 다음 요소는 다음 반복에서 확인되지 않습니다. 이 경우에는 다음을 수행해야합니다 (int i = lStringList.size (); i> -1; i--)
Johntor

1
동의하다! 대안은 i--; for 루프 내의 if 조건에서.
suhas0sn07

이 답변은 위의 의견에서 문제를 해결하기 위해 편집 된 것으로 생각합니다. 지금은 적어도 나에게는 잘 작동합니다.
Kira Resari

11

전통적인 방식으로 배열을 반복해야합니다.

목록에서 요소를 제거 할 때마다 이후의 요소가 푸시됩니다. 반복 요소 이외의 요소를 변경하지 않는 한 다음 코드가 작동합니다.

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}

10

Java 8에서는 Collection Interface를 사용하고 removeIf 메소드를 호출하여이를 수행 할 수 있습니다.

yourList.removeIf((A a) -> a.value == 2);

자세한 내용은 여기를 참조하십시오


6

정상적인 방법으로 루프를 수행하십시오 java.util.ConcurrentModificationException. 액세스되는 요소와 관련된 오류입니다.

그래서 시도하십시오 :

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}

java.util.ConcurrentModificationException목록에서 아무것도 제거하지 않아서 피했습니다 . 교활한. :)리스트를 반복하기 위해 이것을 "일반적인 방법"이라고 부를 수는 없습니다.
Zsolt Sky

6

목록을 반복하는 동안 요소를 제거하려는 경우 가능합니다. 내 예를 아래에서 보자.

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

위의 배열 목록 이름이 있습니다. 그리고 위 목록에서 "def"이름을 제거하고 싶습니다.

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

위의 코드는 ConcurrentModificationException을 발생 시킵니다.반복하는 동안 목록을 수정하기 때문에 예외를 발생시킵니다.

이런 식으로 Arraylist에서 "def"이름을 제거하려면

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

위 코드는 반복자를 통해 Arraylist에서 "def"이름을 제거하고 배열을 인쇄하려고하면 아래 출력을 볼 수 있습니다.

출력 : [abc, ghi, xyz]


그렇지 않으면 동시 패키지에서 사용 가능한 동시 목록을 사용할 수 있으므로 반복하는 동안 제거 및 추가 작업을 수행 할 수 있습니다. 예를 들어 아래 코드 스 니펫을 참조하십시오. ArrayList <문자열> 이름 = 새 ArrayList <문자열> (); CopyOnWriteArrayList <문자열> copyNames = 새 CopyOnWriteArrayList <문자열> (이름); for (문자열 이름 : copyNames) {if (name.equals ( "def")) {copyNames.remove ( "def"); }}
Indra K

CopyOnWriteArrayList는 가장 비용이 많이 드는 작업입니다.
Indra K

5

한 가지 옵션은 removeA방법을 다음과 같이 수정하는 것입니다.

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

그러나 이것은 당신을 의미합니다 doSomething() 을 통과 할 수있을 것 iterator받는 remove방법. 좋은 생각이 아닙니다.

당신은 두 단계 접근 방식에서이 작업을 수행 할 수 있습니다 : 첫 번째 루프에서는 반복 처리 목록을 통해 대신 선택한 요소를 제거하는이 때 표시 로를 삭제할 . 이를 위해 이러한 요소 (얕은 복사본)를 다른 요소로 간단히 복사 할 수 있습니다 List.

그런 다음 반복이 완료되면 removeAll첫 번째 목록에서 두 번째 목록의 모든 요소를 간단히 수행 하십시오.


훌륭하지만, 두 번 반복하지만 동일한 접근 방식을 사용했습니다. 그것은 사물의 간단하고 :) 그것과 전혀 동시 문제를 만든다
판 카즈 Nimgade에게

1
Iterator에 remove (a) 메소드가 있음을 알 수 없습니다. remove ()는 인수를 취하지 않습니다 docs.oracle.com/javase/8/docs/api/java/util/Iterator.html 내가 무엇을 놓치고 있습니까?
c0der

5

다음은 다른 목록을 사용하여 제거 할 객체를 추가 한 다음 stream.foreach를 사용하여 원래 목록에서 요소를 제거하는 예입니다.

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}

두 루프를 실행하는 추가 작업을 수행하고 있다고 생각합니다. 최악의 경우 루프는 전체 목록에 속합니다. 하나의 루프에서만 가장 간단하고 저렴합니다.
Luis Carlos

첫 번째 루프 내에서 객체를 제거 할 수 있다고 생각하지 않으므로 추가 제거 루프가 필요하고 제거 루프는 제거를위한 객체 일뿐입니다. 아마도 하나의 루프로 예제를 작성할 수 있습니다.보고 싶습니다. 감사합니다 @ LuisCarlos
serup

이 코드로 말한 것처럼 for-loop 내부의 요소는 java.util.ConcurrentModificationException 예외를 유발하므로 제거 할 수 없습니다. 그러나 기본을 사용할 수 있습니다. 여기에서는 코드의 일부를 사용하여 예제를 작성합니다.
Luis Carlos

1
for (int i = 0; i <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime ()-customersTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000 % 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} 중요한 것은 i입니다. 어떤 순간도 건너 뛰기를 원하지 않기 때문입니다. 또한 ArrayList 클래스에서 제공하는 removeIf (Predicate <? super E> filter) 메소드를 사용할 수 있습니다. 도움이
Luis Carlos

1
for 루프에는 목록의 반복자에 대한 활성 참조가 있기 때문에 예외가 발생합니다. 일반적으로 참조가 없으며 데이터를 유연하게 변경할 수 있습니다. 도움이
Luis Carlos

3

각 루프마다를 사용하는 대신 일반 for 루프를 사용하십시오. 예를 들어, 아래 코드는 java.util.ConcurrentModificationException을 제공하지 않고 배열 목록의 모든 요소를 ​​제거합니다. 사용 사례에 따라 루프의 조건을 수정할 수 있습니다.

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }

2

다음과 같이 간단한 것을 수행하십시오.

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}

2

스트림을 사용하는 대체 Java 8 솔루션 :

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

Java 7에서는 Guava를 대신 사용할 수 있습니다.

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

구아바 예제는 원하는 목록 일 수도 있고 아닐 수도있는 불변 목록을 생성합니다.


1

ArrayList 대신 CopyOnWriteArrayList를 사용할 수도 있습니다. 이것은 JDK 1.5 이후의 최신 권장 접근 방식입니다.


1

제 경우에는 허용 된 답변이 작동하지 않습니다. 예외를 중지하지만 내 목록에 약간의 불일치가 발생합니다. 다음 솔루션이 완벽하게 작동합니다.

List<String> list = new ArrayList<>();
List<String> itemsToRemove = new ArrayList<>();

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

이 코드에서는 제거 할 항목을 다른 목록에 추가 한 다음 list.removeAll메소드를 사용 하여 필요한 모든 항목을 제거했습니다.


0

"목록을 먼저 복제해야합니까?"

이것이 가장 쉬운 해결책이며 복제본에서 제거하고 제거 후 복제본을 다시 복사하십시오.

내 rummikub 게임의 예 :

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}

2
Downvoter는 최소한 downvoting 전에 의견을 남겨야합니다.
OneWorld

2
이 대답에는 본질적으로 잘못된 것이 없으며 아마도 stones = (...) clone.clone();불필요 할 것입니다 . 하지 않을까요 stones = clone;동일한 작업을 수행?
vikingsteve

두 번째 복제는 불필요합니다. 복제를 반복하고에서 요소를 직접 제거하여이를 단순화 할 수 있습니다 stones. 이렇게하면 clone변수 가 필요하지 않습니다 . for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky

0

목표가 목록에서 모든 요소를 ​​제거하는 것이라면 각 항목을 반복 한 후 다음을 호출 할 수 있습니다.

list.clear()

0

나는 늦게 도착했지만 나는이 솔루션이 간단하고 우아하다고 생각하기 때문에 이것에 대답합니다.

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

이 모든 것은 하나의 목록에서 다른 목록으로 업데이트하기위한 것이며 하나의 목록에서 모두 만들 수 있으며 방법 업데이트에서 목록을 모두 확인하고 목록 사이에서 요소를 지우거나 추가 할 수 있습니다. 이것은 두 목록이 항상 같은 크기임을 의미합니다.


0

배열 목록 대신 반복자를 사용하십시오.

유형이 일치하는 세트를 반복자로 변환하십시오.

다음 요소로 이동하여 제거하십시오.

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

요소를 제거하려면 색인이 필요하므로 다음으로 이동하는 것이 중요합니다.


0

무엇에 대한

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.