CodeGym /Cursos /JAVA 25 SELF /Remoção segura de elementos

Remoção segura de elementos

JAVA 25 SELF
Nível 28 , Lição 2
Disponível

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():
    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}
    
    E com Java 8+ tudo fica bem mais simples:
    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
for-each + remove()
Java 5+ - -
Iterator + remove()
Java 5+ + +
ListIterator
Java 5+ + ++
removeIf
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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION