1. O problema ConcurrentModificationException
Nesta aula não haverá (quase) nada de novo. Mas ela é muito importante, pois a remoção descuidada de dados está entre os erros mais irreparáveis — especialmente em produção. E haverá, sim, uma classe interessante que você provavelmente vai adorar!
Então, sim, mais uma vez: nenhum for-each para remover! Por mais que você goste dele.
Vamos começar com um exemplo clássico, que causa dor de cabeça em muitos iniciantes (e não apenas neles):
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
// Vamos tentar remover todos os números pares
for (Integer n : numbers) {
if (n % 2 == 0) {
numbers.remove(n); // BUM! ConcurrentModificationException
}
}
Parece que tudo deveria funcionar, mas, na prática, o programa lança uma exceção:
Exception in thread "main" java.util.ConcurrentModificationException
Agora vamos entender em detalhes o que aconteceu aqui. Quando você percorre uma coleção com for-each (ou um Iterator comum), a coleção mantém internamente um “contador de modificações”. Se durante a iteração a coleção é alterada não pelo próprio iterador, esse contador detecta “interferência externa” e lança a exceção. É uma proteção contra erros, para que o programa não trabalhe com uma estrutura de dados corrompida.
2. Uso de Iterator
Como remover elementos corretamente durante a iteração?
Lembrando: Iterator é um objeto especial que permite percorrer a coleção e remover elementos com segurança “em tempo real”. Ele é como um garçom que não só serve os pratos, mas também pode tirar um da mesa durante o serviço.
Obtendo o iterador
Iterator<Integer> it = numbers.iterator();
Iterando com while e removendo via it.remove()
Aqui está a forma correta de remover todos os números pares de uma lista:
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
Integer n = it.next();
if (n % 2 == 0) {
it.remove(); // Remoção segura do elemento atual
}
}
System.out.println(numbers); // [1, 3, 5]
Ponto importante: só é possível remover elementos pelo próprio iterador (it.remove()) e somente após chamar it.next(). Se você tentar chamar remove() duas vezes seguidas sem next(), receberá IllegalStateException.
3. ListIterator: recursos avançados
Aqui está a novidade anunciada no início! ListIterator é um iterador “turbinado” para listas (List) que permite não só remover, mas também adicionar elementos durante a iteração, além de se mover nos dois sentidos (para frente e para trás).
Diferenças em relação ao Iterator comum
- Iterator — direto e inflexível: só segue em frente, apenas remoção.
- ListIterator — flexível e versátil: vai para frente e para trás, remove, adiciona via add() e ainda pode substituir o elemento atual com o método set().
Exemplo: remoção e adição de elementos
List<String> words = new ArrayList<>(List.of("cat", "dog", "bird"));
ListIterator<String> it = words.listIterator();
while (it.hasNext()) {
String word = it.next();
if (word.length() == 3) {
it.remove(); // Removemos palavras de 3 letras
it.add("pet"); // Em seguida adicionamos "pet" após a palavra removida
}
}
System.out.println(words); // [pet, pet, bird]
Observação: a inserção via it.add() coloca o elemento imediatamente após a posição atual do iterador.
4. Remoção com removeIf
A partir do Java 8, surgiu o método conciso e prático removeIf. Ele recebe uma expressão lambda (ou qualquer Predicate) e remove todos os elementos para os quais a condição retorna true.
Exemplo: remover todos os números pares
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
numbers.removeIf(n -> n % 2 == 0);
System.out.println(numbers); // [1, 3, 5]
Isso não é apenas mais curto, mas também seguro: por dentro, o método usa o iterador apropriado — nenhum ConcurrentModificationException ocorrerá.
Exemplo: remover strings com menos de 3 caracteres
List<String> words = new ArrayList<>(List.of("hi", "cat", "no", "elephant"));
words.removeIf(word -> word.length() < 3);
System.out.println(words); // [cat, elephant]
Dica: se você precisa apenas remover elementos por uma condição, use removeIf. É a forma mais concisa e moderna.
5. Recomendações práticas
Qual abordagem é preferível?
- Se você precisa remover elementos por uma condição e usa Java 8+: use removeIf — curto, claro, seguro.
- Se você está em uma versão antiga do Java ou precisa de uma lógica de iteração mais complexa: use Iterator e seu método remove().
- Se você trabalha com List e quer não só remover, mas também adicionar elementos durante a iteração: use ListIterator.
Particularidades para diferentes tipos de coleções
- List: suporta todas as abordagens descritas (Iterator, ListIterator, removeIf).
- Set: não possui índices, mas o Iterator padrão e removeIf funcionam.
- Map: para remover por condição, use um iterador sobre o entrySet():
E com Java 8+ tudo fica bem mais simples:Map<String, Integer> map = new HashMap<>(Map.of("a", 1, "b", 2, "c", 3)); Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Integer> entry = it.next(); if (entry.getValue() % 2 == 0) { it.remove(); } } System.out.println(map); // {a=1, c=3}map.entrySet().removeIf(entry -> entry.getValue() % 2 == 0);
6. Exemplo prático: filtragem de usuários
Suponha que temos uma lista de usuários e queremos remover todos os usuários com menos de 18 anos.
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
List<User> users = new ArrayList<>(List.of(
new User("Anya", 17),
new User("Boris", 20),
new User("Vika", 15),
new User("Gleb", 25)
));
// Removemos os menores de idade com removeIf
users.removeIf(user -> user.age < 18);
System.out.println(users); // [Boris (20), Gleb (25)]
7. Comparação das abordagens
Vamos montar uma pequena tabela para fixar. Nosso cérebro gosta disso.
| Abordagem | Suportado desde a versão | Concisão | Segurança | Flexibilidade |
|---|---|---|---|---|
|
Java 5+ | - | ❌ | - |
|
Java 5+ | + | ✅ | + |
|
Java 5+ | + | ✅ | ++ |
|
Java 8+ | ++ | ✅ | + |
8. Erros comuns ao remover elementos de coleções
Erro nº 1: tentar remover elementos em for-each
for (String s : list) {
if (s.equals("test")) {
list.remove(s);
}
}
Você já sabe: não faça isso — você vai obter ConcurrentModificationException! Use um iterador ou removeIf.
Erro nº 2: chamar remove() no iterador sem next()
Iterator<String> it = list.iterator();
it.remove(); // IllegalStateException — não é possível remover antes de chamar next()
Erro nº 3: tentar remover elementos de uma coleção que não pode ser modificada
List<String> immutable = List.of("a", "b", "c");
immutable.removeIf(s -> s.equals("a")); // UnsupportedOperationException
Métodos de remoção não são suportados em coleções imutáveis.
Erro nº 4: tentar remover elementos de Map via values() ou keySet() sem iterador
for (String key : map.keySet()) {
if (key.startsWith("a")) {
map.remove(key); // ConcurrentModificationException!
}
}
Use um iterador por entrySet() ou removeIf.
GO TO FULL VERSION