Como evitar java.util.ConcurrentModificationException ao iterar e remover elementos de um ArrayList


203

Eu tenho um ArrayList que eu quero iterar. Enquanto iterando sobre isso, tenho que remover elementos ao mesmo tempo. Obviamente, isso gera um java.util.ConcurrentModificationException.

Qual é a melhor prática para lidar com esse problema? Devo clonar a lista primeiro?

Eu removo os elementos não no próprio loop, mas em outra parte do código.

Meu código fica assim:

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 pode ligar Test.removeA() ;


Respostas:


325

Duas opções:

  • Crie uma lista de valores que você deseja remover, adicione a essa lista dentro do loop e chame originalList.removeAll(valuesToRemove)no final
  • Use o remove()método no próprio iterador. Observe que isso significa que você não pode usar o loop for aprimorado.

Como um exemplo da segunda opção, removendo quaisquer cadeias com um comprimento maior que 5 de uma lista:

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
Eu deveria ter mencionado que removo os elementos em outra parte do código e não o próprio loop.
RoflcoptrException

@Roflcoptr: Bem, é difícil responder sem ver como os dois bits de código interagem. Basicamente, você não pode fazer isso. Não é óbvio se a clonagem da lista ajudaria primeiro, sem ver como tudo se encaixa. Você pode fornecer mais detalhes em sua pergunta?
Jon Skeet

Sei que a clonagem da lista ajudaria, mas não sei se é uma boa abordagem. Mas vou adicionar um pouco mais de código.
RoflcoptrException

2
Essa solução também leva ao java.util.ConcurrentModificationException, consulte stackoverflow.com/a/18448699/2914140 .
CoolMind

1
@CoolMind: sem vários threads, esse código deve estar correto.
Jon Skeet

17

Dos JavaDocs do ArrayList

Os iteradores retornados pelos métodos iterator e listIterator dessa classe são rápidos: se a lista for modificada estruturalmente a qualquer momento após a criação do iterador, de qualquer forma, exceto pelos métodos de remoção ou adição do próprio iterador, o iterador lançará uma ConcurrentModificationException.


6
e onde está a resposta para a pergunta?
Adelin

Como ele diz, a não ser através própria remove do iterador ou adicionar métodos
Varun Acar

14

Você está tentando remover o valor da lista no avançado "for loop", o que não é possível, mesmo se você aplicar algum truque (o que você fez no seu código). A melhor maneira é codificar o nível do iterador, conforme recomendado aqui.

Eu me pergunto como as pessoas não sugeriram a abordagem tradicional para loop.

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

Isso também funciona.


2
Isso não está correto!!! quando você remove um elemento, o próximo fica em sua posição e, enquanto eu aumenta, o próximo elemento não é verificado na próxima iteração. Neste caso, você deve ir para (int i = lStringList.size (); i> 1; i--)
Johntor

1
Aceita! Alternativo é executar i--; na condição if dentro do loop.
suhas0sn07

Penso que esta resposta foi editada para resolver os problemas nos comentários acima, por isso agora funciona bem, pelo menos para mim.
Kira Resari

11

Você realmente deve apenas repetir a matriz da maneira tradicional

Sempre que você remover um elemento da lista, os elementos posteriores serão enviados adiante. Contanto que você não altere outros elementos além do iterativo, o código a seguir deve funcionar.

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

No Java 8, você pode usar a Interface de coleção e fazer isso chamando o método removeIf:

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

Mais informações podem ser encontradas aqui


6

Faça o loop da maneira normal, o java.util.ConcurrentModificationException é um erro relacionado aos elementos que são acessados.

Então tente:

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

Você evitou java.util.ConcurrentModificationExceptionnão remover nada da lista. Complicado. :) Você não pode realmente chamar isso de "o caminho normal" para iterar uma lista.
Zsolt Sky

6

Ao iterar a lista, se você deseja remover o elemento, é possível. Vamos ver abaixo meus exemplos,

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

Eu tenho os nomes acima da lista Array. E eu quero remover o nome "def" da lista acima,

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

O código acima lança a exceção ConcurrentModificationException porque você está modificando a lista durante a iteração.

Portanto, para remover o nome "def" do Arraylist dessa maneira,

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

O código acima, através do iterador, podemos remover o nome "def" da Arraylist e tentar imprimir a matriz; você verá a saída abaixo.

Saída: [abc, ghi, xyz]


Senão, podemos usar a lista simultânea que está disponível no pacote simultâneo, para que você possa executar operações de remoção e adição durante a iteração. Por exemplo, veja o trecho de código abaixo. ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <> copyNames = new CopyOnWriteArrayList <> (nomes); for (String name: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K

CopyOnWriteArrayList será operações mais caras.
Indra K

5

Uma opção é modificar o removeAmétodo para isso -

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

Mas isso significa que você doSomething()deve ser capaz de passar iteratoro removemétodo. Não é uma boa ideia.

Você pode fazer isso na abordagem em duas etapas: No primeiro loop, quando você iterar sobre a lista, em vez de remover os elementos selecionados, marque- os como a serem excluídos . Para isso, você pode simplesmente copiar esses elementos (cópia superficial) em outro List.

Depois que sua iteração estiver concluída, basta fazer a removeAllpartir da primeira lista todos os elementos na segunda lista.


Excelente, usei a mesma abordagem, apesar de repetir duas vezes. isso torna as coisas simples e sem problemas simultâneos com ele :)
Pankaj Nimgade

1
Não vejo que o Iterator tenha um método remove (a). O remove () não aceita argumentos docs.oracle.com/javase/8/docs/api/java/util/Iterator.html o que estou perdendo?
C0der

5

Aqui está um exemplo em que eu uso uma lista diferente para adicionar os objetos a serem removidos e depois uso stream.foreach para remover elementos da lista original:

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

Eu acho que você está fazendo um trabalho extra executando dois loops, no pior caso, os loops seriam da lista inteira. Seria mais simples e mais barato fazê-lo em apenas um loop.
Luis Carlos

Eu não acho que você pode remover o objeto do primeiro loop, daí a necessidade de um loop de remoção extra, também o loop de remoção é apenas objetos para remoção - talvez você possa escrever um exemplo com apenas um loop, gostaria de vê-lo - obrigado @ LuisCarlos
serup 11/11/16

Como você diz com esse código, não é possível remover nenhum elemento dentro do loop for porque causa a exceção java.util.ConcurrentModificationException. No entanto, você pode usar um básico para. Aqui eu escrevo um exemplo usando parte do seu código.
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--); }} É importante i-- porque você não quer pular nenhum elemento. Além disso, você pode usar o método removeIf (filtro Predicate <? Super E>) fornecido pela classe ArrayList. Espero que isso ajuda
Luis Carlos

1
A exceção ocorre porque no loop for existe como uma referência ativa ao iterador da lista. Normalmente, não há uma referência e você tem mais flexibilidade para alterar os dados. Espero que isso ajuda
Luis Carlos

3

Em vez de usar Para cada loop, use normal para loop. por exemplo, o código abaixo remove todo o elemento na lista de matrizes sem fornecer java.util.ConcurrentModificationException. Você pode modificar a condição no loop de acordo com seu caso de uso.

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

          e.remove(i);
        }

2

Faça algo simples como este:

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

2

Uma solução Java 8 alternativa usando stream:

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

No Java 7, você pode usar o Guava:

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

Observe que o exemplo do Guava resulta em uma lista imutável que pode ou não ser o que você deseja.


1

Você também pode usar CopyOnWriteArrayList em vez de um ArrayList. Essa é a mais recente abordagem recomendada do JDK 1.5 em diante.


1

No meu caso, a resposta aceita não está funcionando, ela interrompe a exceção, mas causa alguma inconsistência na minha lista. A solução a seguir está funcionando perfeitamente para mim.

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

Neste código, adicionei os itens a serem removidos em outra lista e, em seguida, usei o list.removeAllmétodo para remover todos os itens necessários.


0

"Devo clonar a lista primeiro?"

Essa será a solução mais fácil, remova-a do clone e copie-o novamente após a remoção.

Um exemplo do meu jogo 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
Os que recusam votos devem pelo menos deixar um comentário antes de votar.
OneWorld

2
Não há nada inerentemente errado com essa resposta, talvez stones = (...) clone.clone();seja supérfluo. Não stones = clone;faria o mesmo?
vikingsteve

Eu concordo, a segunda clonagem é desnecessária. Você pode simplificar ainda mais iterando no clone e removendo elementos diretamente de stones. Dessa forma, você nem precisa da clonevariável: # for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky

0

Se seu objetivo é remover todos os elementos da lista, você pode iterar sobre cada item e chamar:

list.clear()

0

Chego tarde, sei, mas respondo a isso porque acho que essa solução é simples e elegante:

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

Tudo isso é para atualizar de uma lista para outra e você pode fazer tudo de apenas uma lista. No método de atualização, você verifica a lista e pode apagar ou adicionar elementos entre a lista. Isso significa que ambos listam sempre o mesmo tamanho


0

Use o iterador em vez da lista de matrizes

Faça com que um conjunto seja convertido em iterador com correspondência de tipo

E vá para o próximo elemento e remova

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

Mover para o próximo é importante aqui, pois deve levar o índice para remover o elemento.


0

Que tal de

import java.util.Collections;

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.