Todos sabemos que você não pode fazer o seguinte por causa de ConcurrentModificationException
:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
Mas 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<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Obviamente, isso resulta em:
Exception in thread "main" java.util.ConcurrentModificationException
Mesmo que vários threads 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?
Também estou usando um arbitrário Collection
aqui, não necessariamente um ArrayList
, então você não pode confiar get
.
java
collections
iteration
Claudiu
fonte
fonte
Respostas:
Iterator.remove()
é seguro, você pode usá-lo assim: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 maneira enquanto a iteração estiver em andamento.Fonte: docs.oracle> A interface da coleção
Da mesma forma, se você tem um
ListIterator
e deseja adicionar itens, pode usá-loListIterator#add
, pelo mesmo motivo que pode usáIterator#remove
-lo - ele foi projetado para permitir isso.No seu caso, você tentou remover de uma lista, mas a mesma restrição se aplica se você tentar
put
entrar umMap
pouco enquanto itera seu conteúdo.fonte
iterator.next()
chamada no loop for? Se não, alguém pode explicar o porquê?List.add
também é "opcional" no mesmo sentido, mas você não diria que é "inseguro" adicionar a uma lista.Isso funciona:
Eu assumi que, como um loop foreach é um açúcar sintático para a iteração, o uso de um iterador não ajudaria ... mas fornece essa
.remove()
funcionalidade.fonte
Com o Java 8, você pode usar o novo
removeIf
método . Aplicado ao seu exemplo:fonte
removeIf
usaIterator
ewhile
loop. Você pode vê-lo em java 8java.util.Collection.java
ArrayList
substituí-lo por motivos de desempenho. O que você está se referindo é apenas a implementação padrão.equals
não é usado aqui, por isso não precisa ser implementado. (Mas é claro, se você usarequals
em seu teste, ele deverá ser implementado da maneira que você deseja.) #Como a pergunta já foi respondida, ou seja, a melhor maneira é usar o método remove do objeto iterador, eu entraria nas especificidades do local em que o erro
"java.util.ConcurrentModificationException"
é gerado.Toda classe de coleção tem uma classe privada que implementa a interface Iterator e fornece métodos como
next()
,remove()
ehasNext()
.O código para o próximo se parece com isso ...
Aqui o método
checkForComodification
é implementado comoPortanto, como você pode ver, se tentar explicitamente remover um elemento da coleção. Isso resulta em
modCount
ficar diferente deexpectedModCount
, resultando na exceçãoConcurrentModificationException
.fonte
Você pode usar o iterador diretamente como você mencionou, ou manter uma segunda coleção e adicionar cada item que deseja remover à nova coleção e, em seguida, removeAll no final. Isso permite que você continue usando a segurança de tipo do loop for-each ao custo de maior uso de memória e tempo de CPU (não deve ser um grande problema, a menos que você tenha listas realmente grandes ou um computador muito antigo)
fonte
Nesses casos, um truque comum é (foi?) Retroceder:
Dito isto, estou mais do que feliz por você ter maneiras melhores no Java 8, por exemplo,
removeIf
oufilter
em fluxos.fonte
ArrayList
coleções similares.for(int i = l.size(); i-->0;) {
?A mesma resposta que Claudius com um loop for:
fonte
Com o Eclipse Collections , o método
removeIf
definido em MutableCollection funcionará:Com a sintaxe do Java 8 Lambda, isso pode ser escrito da seguinte maneira:
A chamada para
Predicates.cast()
é necessária aqui porque umremoveIf
método padrão foi incluído najava.util.Collection
interface no Java 8.Nota: Sou um colaborador das Coleções Eclipse .
fonte
Faça uma cópia da lista existente e itere sobre a nova cópia.
fonte
As pessoas afirmam que não é possível remover de uma coleção que está sendo iterada por um loop foreach. Eu só queria salientar que isso é tecnicamente incorreto e descrever exatamente (eu sei que a pergunta do OP é tão avançada que evita saber disso) o código por trás dessa suposição:
Não é que você não possa remover da iteração
Colletion
e não pode continuar a iteração depois de fazer isso. Daí obreak
código acima.Desculpas se esta resposta for um caso de uso um tanto especializado e mais adequado ao encadeamento original de onde cheguei, que seja marcado como duplicado (apesar de esse encadeamento parecer mais matizado) disso e bloqueado.
fonte
Com um loop for tradicional
fonte
i++
a proteção do loop em vez de dentro do corpo do loop.i++
incremento não eram condicionais - Vejo agora que é por isso que você fazê-lo no corpo :)A
ListIterator
permite adicionar ou remover itens da lista. Suponha que você tenha uma lista deCar
objetos:fonte
previous
método.Eu tenho uma sugestão para o problema acima. Não há necessidade de lista secundária ou tempo extra. Por favor, encontre um exemplo que faria o mesmo, mas de uma maneira diferente.
Isso evitaria a exceção de simultaneidade.
fonte
ArrayList
e, portanto, não pode ser utilizadoget()
. Caso contrário, provavelmente uma boa abordagem, no entanto.Collection
-Collection
não incluiget
. (Embora aList
interface do FWIW inclua 'get').while
-looping aList
. Mas +1 para esta resposta, porque ela veio primeiro.ConcurrentHashMap ou ConcurrentLinkedQueue ou ConcurrentSkipListMap pode ser outra opção, porque eles nunca lançam nenhuma ConcurrentModificationException, mesmo se você remover ou adicionar um item.
fonte
java.util.concurrent
pacote. Algumas outras classes de casos de uso semelhantes / comuns desse pacote sãoCopyOnWriteArrayList
&CopyOnWriteArraySet
[mas não limitadas a elas].ConcurrentModificationException
, usá-los em uma reforçada -por-circuito pode ainda causar problemas de indexação (ou seja: ainda pular elementos, ouIndexOutOfBoundsException
...)Eu sei que esta pergunta é muito antiga para ser sobre o Java 8, mas para aqueles que usam o Java 8 você pode usar facilmente removeIf ():
fonte
Outra maneira é criar uma cópia do seu arrayList:
fonte
i
não é umindex
objeto, mas um objeto. Talvez chamá-loobj
fosse mais adequado.fonte
No caso de ArrayList: remove (int index) - se (index for a posição do último elemento), ele 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);
// leva O (1)while(list.size()>0)list.remove(0);
// leva O (fatorial (n))fonte
O problema é depois de remover o elemento da lista se você pular a chamada iterator.next () interna. ainda funciona! Embora eu não proponha escrever código como este, ajuda a entender o conceito por trás dele :-)
Felicidades!
fonte
Exemplo de modificação de coleção segura de encadeamento:
fonte
Eu sei que esta pergunta assume apenas um
Collection
, e não mais especificamente nenhumList
. Mas, para aqueles que estão lendo esta pergunta que estão de fato trabalhando com umaList
referência, é possível evitarConcurrentModificationException
com umwhile
loop (enquanto modifica nela), se desejar evitarIterator
(ou se deseja evitá-la em geral ou evitá-la especificamente para obter uma ordem de loop diferente da parada do início ao fim em cada elemento [que eu acredito que é a única ordem queIterator
pode fazer]):* Atualização: Veja os comentários abaixo que esclarecem que o análogo também é possível com o tradicional for -loop.
Não ConcurrentModificationException desse código.
Aí vemos que o loop não começa no começo e não pára em todos os elementos (o que eu acredito
Iterator
que não pode fazer).FWIW também vemos
get
ser chamadolist
, o que não poderia ser feito se sua referência fosse justaCollection
(em vez doList
tipo mais específico deCollection
) -List
interface incluiget
, masCollection
interface não. Se não fosse essa diferença, alist
referência poderia ser umaCollection
[e, portanto, tecnicamente essa resposta seria uma resposta direta, em vez de uma resposta tangencial].O mesmo código do FWIWW ainda funciona após a modificação para começar do início ao fim de cada elemento (assim como a
Iterator
ordem):fonte
ConcurrentModificationException
, mas não o loop for for tradicional (que a outra resposta usa) - sem perceber que antes era por isso que eu estava motivado a escrever essa resposta (eu erroneamente pensava então que eram todos os loops que lançariam a exceção).Uma solução poderia ser girar a lista e remover o primeiro elemento para evitar ConcurrentModificationException ou IndexOutOfBoundsException
fonte
Experimente este (remove todos os elementos da lista iguais
i
):fonte
você também pode usar recursão
A recursão em java é um processo no qual um método se chama continuamente. Um método em java que se chama é chamado de método recursivo.
fonte
essa pode não ser a melhor maneira, mas para a maioria dos casos pequenos isso deve ser aceitável:
Eu não me lembro de onde li isso ... por razões de justiça, farei este wiki na esperança de que alguém o encontre ou apenas para não ganhar reputação que eu não mereço.
fonte