Aikido

Por que você deve usar padrões seguros ao remover itens de coleções

Legibilidade

Regra
Utilização seguros métodos ao remover de coleções.
Modificar uma coleção enquanto iterar sobre ela muitas vezes causa erros.

Linguagens suportadas: PY, Java, C/C++, C#, 
Swift/Objective-C, Ruby, PHP, Kotlin, Go,
Scala, Rust, Groovy, Dart, Julia, Elixit, 
Erlang, Clojure, OCaml, Lua

Introdução

Remover itens de uma coleção durante a iteração causa exceções de modificação concorrente em Java e comportamento imprevisível em C#. O iterador mantém um ponteiro interno que se torna inválido quando a coleção subjacente é alterada. Isso leva a elementos ignorados, falhas ou loops infinitos, dependendo do tipo de coleção e do padrão de remoção utilizados.

Por que isso importa

Estabilidade do sistema: Exceções de modificação concorrente travam a aplicação imediatamente. Em produção, isso significa requisições perdidas e indisponibilidade do serviço. A exceção frequentemente ocorre em casos de borda com dados específicos, tornando-a difícil de detectar durante os testes.

Integridade dos dados: Quando a lógica de remoção falha no meio de uma iteração, a coleção é deixada em um estado parcialmente modificado. Alguns itens são removidos enquanto outros que deveriam ter sido removidos permanecem. Isso cria dados inconsistentes que afetam a lógica a jusante.

Complexidade de depuração: Bugs de modificação concorrente dependem de tempo e podem se manifestar apenas com certas combinações de dados. São difíceis de reproduzir consistentemente, tornando-os complexos de depurar e corrigir de forma confiável.

Exemplos de código

❌ Não-conforme:

List<User> users = getUserList();
for (User user : users) {
    if (!user.isActive()) {
        users.remove(user); // ConcurrentModificationException
    }
}

Por que está errado: Removendo de usuários enquanto a iteração com for-loop aprimorado causa ConcurrentModificationException. O iterador detecta que a coleção foi modificada fora do iterador e lança uma exceção imediatamente. Quaisquer usuários ativos após o primeiro inativo nunca são processados.

✅ Compatível:

List<User> users = getUserList();
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
    User user = iterator.next();
    if (!user.isActive()) {
        iterator.remove(); // Safe removal through iterator
    }
}

Por que isso importa: Utilizando iterator.remove() remove itens com segurança durante a iteração. O iterador mantém um estado consistente e continua processando os itens restantes. Todos os usuários inativos são removidos corretamente, sem exceções.

Conclusão

Utilize iteradores remove() método para remoção segura durante a iteração. Alternativamente, use streams com filter() para criar novas coleções ou removeIf() para remoção em massa. Nunca chame a coleção de remove() diretamente durante a iteração.

FAQs

Dúvidas?

E quanto ao uso de loops for regulares com índice?

Iterar para trás com índice funciona: for (int i = list.size() - 1; i >= 0; i--). Remover itens desloca elementos subsequentes, mas a iteração para trás evita pular. No entanto, iterator.remove() ou removeIf() são mais claros e menos propensos a erros.

Posso usar Java 8+ removeIf em vez disso?

Sim, users.removeIf(user -> !user.isActive()) é a abordagem moderna preferida. É mais concisa e lida com a iteração de forma segura internamente. Use removeIf() ao remover com base em um predicado, streams para transformações e métodos de iterador quando a lógica de remoção for complexa.

Isso se aplica a todos os tipos de coleção?

Sim, ArrayList, HashSet, HashMap e a maioria das coleções lançam ConcurrentModificationException quando modificadas durante a iteração. Coleções thread-safe como ConcurrentHashMap permitem modificação, mas possuem semânticas diferentes. Sempre verifique a documentação da coleção para as regras de modificação.

E quanto a coleções C#?

C# lança InvalidOperationException quando coleções são modificadas durante a iteração. Use ToList() para criar uma cópia antes de iterar: foreach (var user in users.ToList()) e então remova do original. Ou use LINQ: users = users.Where(u => u.IsActive).ToList().

Como remover múltiplos itens de forma eficiente?

Use `removeIf()` para coleções únicas ou streams para filtragem complexa: `users = users.stream().filter(User::isActive).collect(Collectors.toList())`. Essas abordagens são otimizadas para operações em massa e mais eficientes do que remover itens um por um em um loop.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.