sort - o que são coleções em java




Iterando através de uma coleção, evitando ConcurrentModificationException ao modificar(remover) em loop (15)

Nós todos sabemos que você não pode fazer isso:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc ... isso aparentemente funciona às vezes, mas nem sempre. Aqui está um código específico:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

Isso, claro, resulta em:

Exception in thread "main" java.util.ConcurrentModificationException

... mesmo que vários segmentos não estejam fazendo isso ... De qualquer forma.

Qual a melhor solução para esse problema? Como posso remover um item da coleção em um loop sem lançar essa exceção?

Eu também estou usando uma Collection arbitrária aqui, não necessariamente uma ArrayList , então você não pode confiar em get .


A melhor maneira (Recomendado) é o uso do pacote java.util.Concurrent. Ao usar este pacote, você pode facilmente evitar essa exceção. referir código modificado

public static void main(String[] args) {
        Collection<Integer> l = new CopyOnWriteArrayList<Integer>();

        for (int i=0; i < 10; ++i) {
            l.add(new Integer(4));
            l.add(new Integer(5));
            l.add(new Integer(6));
        }

        for (Integer i : l) {
            if (i.intValue() == 5) {
                l.remove(i);
            }
        }

        System.out.println(l);
    }

As pessoas estão afirmando que uma não pode ser removida de uma coleção sendo iterada por um loop foreach. Eu só queria salientar que é tecnicamente incorreto e descrever exatamente (eu sei que a pergunta do OP é tão avançada a ponto de evitar saber disso) o código por trás dessa suposição:

    for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
        if (obj.isTouched()) {
            untouchedSet.remove(obj);
            touchedSt.add(obj);
            break;  // this is key to avoiding returning to the foreach
        }
    }

Não é que você não possa remover da Colletion iterada, Colletion sim que você não pode continuar a iteração depois disso. Daí a break no código acima.

Desculpas se esta resposta é um caso de uso um tanto especialista e mais adequado para o thread original que cheguei aqui, que um é marcado como um duplicado (apesar deste segmento aparecendo mais nuances) disso e bloqueado.


Com o Java 8, você pode usar o novo método removeIf . Aplicado ao seu exemplo:

Collection<Integer> coll = new ArrayList<Integer>();
//populate

coll.removeIf(i -> i.intValue() == 5);

Com um loop tradicional

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }

Em tais casos, um truque comum é (foi?) Retroceder:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

Dito isso, estou mais do que feliz por você ter maneiras melhores no Java 8, por exemplo, removeIf ou filter on streams.


Eu sei que esta questão é muito antiga para ser sobre o Java 8, mas para aqueles que usam o Java 8 você pode facilmente usar o removeIf ():

Collection<Integer> l = new ArrayList<Integer>();

for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
}

l.removeIf(i -> i.intValue() == 5);

Exemplo de modificação de coleção segura de thread:

public class Example {
    private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());

    public void removeFromQueue() {
        synchronized (queue) {
            Iterator<String> iterator = queue.iterator();
            String string = iterator.next();
            if (string.isEmpty()) {
                iterator.remove();
            }
        }
    }
}

Faça uma cópia da lista existente e repita a nova cópia.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

Mesma resposta que Claudius com um loop for:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

No caso de ArrayList: remove (int index) - if (index é a posição do último elemento) evita sem System.arraycopy() e não leva tempo para isso.

O tempo de arraycopy aumenta se (o índice diminui), a propósito os elementos da lista também diminuem!

a melhor maneira eficaz de remover é remover seus elementos em ordem decrescente: while(list.size()>0)list.remove(list.size()-1); // pega O (1) while(list.size()>0)list.remove(0); // pega O (fatorial (n))

//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
    Integer integer = rdm.nextInt();
    ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion

// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++) 
   if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion

// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
   if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion

// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
    if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
  • para loop de índice: 1090 ms
  • para desc index: 519 msec --- o melhor
  • para o iterador: 1043 ms

Você pode iterar a lista usando for-loop e você precisa chamar list.remove (0). Você precisa codificar o índice com o parâmetro de índice de remoção com zero. Veja também esta resposta :

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
    list.remove(0);
}

Você pode usar o iterador diretamente, como você mencionou, ou então manter uma segunda coleção e adicionar cada item que você deseja remover para a nova coleção, então removeAll no final. Isso permite que você continue usando o tipo de segurança do loop for-each ao custo de uso de memória e tempo de CPU maiores (não deve ser um grande problema, a menos que você tenha listas realmente grandes ou um computador realmente antigo)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}


Iterator.remove() é seguro, você pode usá-lo assim:

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

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

Observe que Iterator.remove() é a única maneira segura de modificar uma coleção durante a iteração; o comportamento não é especificado se a coleção subjacente for modificada de qualquer outra forma enquanto a iteração estiver em andamento.

Fonte: docs.oracle.com/javase/tutorial/collections/interfaces/…

E da mesma forma, se você tem um ListIterator e deseja adicionar itens, você pode usar ListIterator#add , pelo mesmo motivo que você pode usar o Iterator#remove - ele é projetado para permitir isso.

No seu caso, você tentou remover de uma lista, mas a mesma restrição se aplica ao tentar put em um Map enquanto iterava seu conteúdo.


Collection<Integer> l = new ArrayList<Integer>();//Do the collection thing...

l.removeIf(i -> i == 5);      //iterates through the collection and removes every occurence of 5

Expressões lambda e métodos de coleta no Jdk 8 vêm em Handy e adicionam um pouco de açúcar sintático 😊.

O método removeIf percorre a coleção e filtra com o Predicate. Um Predicado é função de um argumento que retorna um booleano ... Assim como boolean _bool = (str) -> str.equals("text");





collections