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

Belphegor
fonte
javacodegeeks.com/2011/05/…
bilash.saha 12/11/11

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();
    }
}
Jon Skeet
fonte
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.

Varun Achar
fonte
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.

suhas0sn07
fonte
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);
    }
}
Marcus
fonte
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

ggeo
fonte
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();
}
Tacila
fonte
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]

Indra K
fonte
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.

Bhaskar
fonte
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));
}
serup
fonte
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);
        }
Shubham Chopra
fonte
2

Faça algo simples como este:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}
Xlsx
fonte
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.

Zsolt Sky
fonte
1

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

Pathikreet
fonte
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.

Asad Ali Choudhry
fonte
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();
}
Arjen Rodenhuis
fonte
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()
Gibolt
fonte
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

Becquer Arguello Flores
fonte
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.

user8009263
fonte
0

Que tal de

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());
joseluisbz
fonte
-3

Basta adicionar uma pausa após a instrução ArrayList.remove (A)

Sebastian Altamirano
fonte
Você poderia adicionar alguma explicação?
Xkxzr # 9/18