Chamando remove no loop foreach em Java

Em Java, é legal chamar a remoção em uma coleção ao iterar a coleção usando um loop foreach? Por exemplo:

List names = .... for (String name : names) { // Do something names.remove(name). } 

Como adendo, é legal remover itens que ainda não foram iterados? Por exemplo,

 //Assume that the names list as duplicate entries List names = .... for (String name : names) { // Do something while (names.remove(name)); } 

Para remover com segurança de uma coleção durante a iteração, você deve usar um iterador.

Por exemplo:

 List names = .... Iterator i = names.iterator(); while (i.hasNext()) { String s = i.next(); // must be called before you can call i.remove() // Do something i.remove(); } 

Da documentação do Java :

Os iteradores retornados pelos methods iterator e listIterator dessa class são fail-fast: se a lista for estruturalmente modificada a qualquer momento após a criação do iterador, de qualquer forma, exceto pelos methods remove ou add do iterador, o iterador lançará um ConcurrentModificationException. Assim, em face da modificação concorrente, o iterador falha rapidamente e de forma limpa, em vez de arriscar um comportamento arbitrário e não determinístico em um tempo indeterminado no futuro.

Talvez o que não esteja claro para muitos novatos é o fato de que iterar em uma lista usando as construções for / foreach cria implicitamente um iterador que é necessariamente inacessível. Esta informação pode ser encontrada aqui

Você não quer fazer isso. Isso pode causar um comportamento indefinido dependendo da coleção. Você quer usar um Iterador diretamente. Embora o para cada constructo seja o açúcar sintático e esteja realmente usando um iterador, ele o oculta do código, portanto, você não pode acessá-lo para chamar o Iterator.remove .

O comportamento de um iterador não é especificado se a coleção subjacente for modificada enquanto a iteração estiver em andamento de qualquer outra forma que não seja chamando esse método.

Em vez disso, escreva seu código:

 List names = .... Iterator it = names.iterator(); while (it.hasNext()) { String name = it.next(); // Do something it.remove(); } 

Observe que o código chama Iterator.remove , não List.remove .

Termo aditivo:

Mesmo se você estiver removendo um elemento que ainda não tenha sido iterado, você ainda não deseja modificar a coleção e, em seguida, usar o Iterator . Pode modificar a coleção de uma forma que é surpreendente e afeta operações futuras no Iterator .

O design de java do “enhanced for loop” foi para não expor o iterador ao código, mas a única maneira de remover com segurança um item é acessar o iterador. Então, neste caso, você tem que fazer a velha escola:

  for(Iterator i = names.iterator(); i.hasNext();) { String name = i.next(); //Do Something i.remove(); } 

Se no código real o loop for real realmente valer a pena, você poderá adicionar os itens a uma coleção temporária e chamar removeAll na lista após o loop.

EDIT (reendendum): Não, alterar a lista de alguma forma fora do método iterator.remove () enquanto iterar causará problemas. A única maneira de contornar isso é usar um CopyOnWriteArrayList, mas isso é realmente destinado a problemas de simultaneidade.

O mais barato (em termos de linhas de código) maneira de remover duplicatas é despejar a lista em um LinkedHashSet (e, em seguida, voltar para uma lista, se necessário). Isso preserva a ordem de inserção ao remover duplicatas.

 for (String name : new ArrayList(names)) { // Do something names.remove(nameToRemove); } 

Você clona os names das listas e percorre o clone enquanto remove a lista original. Um pouco mais limpo que a resposta principal.

Eu não sabia sobre iteradores, porém aqui está o que eu estava fazendo até hoje para remover elementos de uma lista dentro de um loop:

 List names = .... for (i=names.size()-1;i>=0;i--) { // Do something names.remove(i); } 

Isso está sempre funcionando e pode ser usado em outros idiomas ou estruturas que não suportam iteradores.

Sim, você pode usar o loop for-each. Para fazer isso, você precisa manter uma lista separada para manter os itens removidos e, em seguida, remover essa lista da lista de nomes usando o método removeAll() .

 List names = .... // introduce a separate list to hold removing items List toRemove= new ArrayList(); for (String name : names) { // Do something: perform conditional checks toRemove.add(name); } names.removeAll(toRemove); // now names list holds expected values 

Aqueles que dizem que você não pode remover com segurança um item de uma coleção, exceto através do Iterator, não são totalmente corretos, você pode fazê-lo com segurança usando uma das collections simultâneas, como ConcurrentHashMap.

Certifique-se de que isso não é cheiro de código. É possível inverter a lógica e ser “inclusivo” em vez de “exclusivo”?

 List names = .... List reducedNames = .... for (String name : names) { // Do something if (conditionToIncludeMet) reducedNames.add(name); } return reducedNames; 

A situação que me levou a essa página envolveu código antigo que passava por uma Lista usando indecisões para remover elementos da Lista. Eu queria refatorar para usar o estilo foreach.

Ele passou por uma lista inteira de elementos para verificar quais deles o usuário tinha permissão para acessar e removeu os que não tinham permissão da lista.

 List services = ... for (int i=0; i 

Para reverter isso e não usar o remover:

 List services = ... List permittedServices = ... for (Service service:services) { if (isServicePermitted(user, service)) permittedServices.add(service); } return permittedServices; 

Quando "remover" seria preferido? Uma consideração é se você tem uma lista grande ou um "add" caro, combinado com apenas alguns removidos em comparação com o tamanho da lista. Pode ser mais eficiente fazer apenas algumas remoções do que muitas outras. Mas no meu caso, a situação não merecia essa otimização.

  1. Tente isto 2. e mude a condição para “WINTER” e você se perguntará:
 public static void main(String[] args) { Season.add("Frühling"); Season.add("Sommer"); Season.add("Herbst"); Season.add("WINTER"); for (String s : Season) { if(!s.equals("Sommer")) { System.out.println(s); continue; } Season.remove("Frühling"); } } 

É melhor usar um Iterator quando quiser remover o elemento de uma lista

porque o código-fonte da remoção é

 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; 

Então, se você remover um elemento da lista, a lista será reestruturada, o índice do outro elemento será alterado, isso pode resultar em algo que você quer que aconteça.

Usar

.remove () do Interator ou

Usar

CopyOnWriteArrayList