CodeGym /Cursos /JAVA 25 SELF /Coleções mutáveis vs imutáveis: diferenças e uso

Coleções mutáveis vs imutáveis: diferenças e uso

JAVA 25 SELF
Nível 34 , Lição 3
Disponível

1. Introdução

Falando de forma simples, uma coleção mutável é aquela que pode ser alterada após a criação: adicionar, remover e modificar elementos. Uma coleção imutável é uma coleção que não pode ser alterada depois de criada. É como concreto após endurecer: dá para olhar, dá para tocar, mas moldar novas formas já não dá.

Uma coleção mutável é como um caderno com lápis: você escreve, apaga, adiciona novas anotações. Uma coleção imutável é como uma página plastificada: ninguém conseguirá mais adicionar ou apagar nada.

Exemplos de coleções mutáveis (mutable)

Em Java, praticamente todas as coleções padrão são mutáveis por padrão. São classes como:

  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • HashMap
  • LinkedHashMap
  • e muitas outras

Exemplo: ArrayList

import java.util.*;

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.set(1, "Charlie"); // substituímos Bob por Charlie
names.remove("Alice");   // removemos Alice
System.out.println(names); // [Charlie]

Aqui podemos fazer o que quisermos com a coleção: adicionar, remover, trocar elementos de lugar. Isso é conveniente quando a coleção é construída dinamicamente, por exemplo, ao ler dados de um arquivo ou da entrada do usuário.

2. Exemplos de coleções imutáveis (immutable)

Você provavelmente já conheceu essas APIs introduzidas no Java 9, mas ainda é preciso se acostumar com elas:

  • List.of(...)
  • Set.of(...)
  • Map.of(...)
  • List.copyOf(collection)
  • Set.copyOf(collection)
  • Map.copyOf(map)

Exemplo: List.of

List<String> planets = List.of("Mercury", "Venus", "Earth", "Mars");
System.out.println(planets); // [Mercury, Venus, Earth, Mars]
planets.add("Jupiter"); // Lançará UnsupportedOperationException!

Tentar modificar a coleção resulta em uma exceção em tempo de execução.

Exemplo: Collections.unmodifiableList

List<String> modifiable = new ArrayList<>(List.of("a", "b"));
List<String> unmodifiable = Collections.unmodifiableList(modifiable);
unmodifiable.add("c"); // UnsupportedOperationException!

Mas há uma pegadinha: se você alterar a coleção original, o wrapper também mudará!

modifiable.add("c");
System.out.println(unmodifiable); // [a, b, c] — o elemento apareceu!

3. Principais diferenças entre coleções mutáveis e imutáveis

Propriedade Mutáveis (mutable) Imutáveis (immutable)
Pode adicionar elemento? Sim Não
Pode remover elemento? Sim Não
Pode modificar elemento? Sim (por exemplo, set) Não
Thread-safety Não (por padrão) Sim (sem estado — nada a alterar)
Pode adicionar null? Sim (normalmente) Não (nos métodos de fábrica do Java 9+)
Implementação ArrayList, HashSet e outras List.of, Set.of, Map.of, copyOf

4. Por que precisamos de coleções imutáveis?

Surge a pergunta: se as coleções mutáveis são tão flexíveis, por que precisamos das imutáveis? Há muitas razões, todas relacionadas à segurança, legibilidade e previsibilidade do código.

Segurança e prevenção de erros

Ao expor uma coleção (por exemplo, a partir de um método ou classe), você quer ter certeza de que ninguém vai alterá-la acidentalmente. Isso é especialmente importante se a coleção contém “dados importantes” que não devem mudar após a inicialização.

Exemplo:

public class Team {
    private final List<String> players;

    public Team(List<String> players) {
        // Fazemos uma cópia imutável para que ninguém possa alterar a composição
        this.players = List.copyOf(players);
    }

    public List<String> getPlayers() {
        return players;
    }
}

Agora, qualquer código que obtiver a lista de jogadores não poderá acrescentar seu “amigo”.

Thread-safety

Coleções mutáveis não são seguras quando acessadas por múltiplas threads simultaneamente. Já as coleções imutáveis podem ser compartilhadas livremente entre threads — ninguém conseguirá estragá-las.

Depuração mais simples

Se a coleção não muda, você sempre sabe o que está dentro dela. Não há o risco de alguém “silenciosamente” alterá-la em outro ponto do código.

Uso como chaves ou valores em outras coleções

Objetos imutáveis são candidatos ideais para uso como chaves em Map ou elementos em Set. Se um objeto puder mudar após ser adicionado, você corre o risco de perder o acesso a ele (veja sobre hashCode e equals).

5. Quando usar coleções mutáveis?

Coleções mutáveis são boas quando:

  • A coleção é construída em etapas, em um loop ou a partir de diferentes fontes.
  • São necessárias mudanças frequentes: adicionar, remover, ordenar.
  • A coleção é para uso interno e ninguém “de fora” poderá estragá-la.

Exemplo: construção de lista

List<String> shoppingList = new ArrayList<>();
shoppingList.add("Leite");
shoppingList.add("Pão");
shoppingList.add("Maçãs");
// Depois de construída — é possível criar uma versão imutável
List<String> finalList = List.copyOf(shoppingList);

6. Quando usar coleções imutáveis?

  • Para armazenar dados constantes (por exemplo, a lista de dias da semana).
  • Para transferir coleções entre camadas da aplicação (por exemplo, do DAO para o serviço).
  • Para retornar coleções de métodos, protegendo-as contra alterações.
  • Para cenários multithread em que a segurança é importante.

Exemplo: dados constantes

public static final List<String> WEEKDAYS = List.of(
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
);

Exemplo: exposição externa

public List<String> getReadOnlyNames() {
    return List.copyOf(names); // ninguém poderá modificar a lista
}

7. Particularidades e armadilhas

Imutabilidade ≠ thread-safety

Uma coleção imutável está protegida contra alterações, mas isso não significa que ela esteja protegida de outros problemas em ambientes multithread (por exemplo, se os elementos da coleção forem objetos mutáveis).

List<List<String>> listOfLists = List.of(new ArrayList<>());
listOfLists.get(0).add("Oops!"); // É possível alterar a lista interna!

Wrappers vs cópias

Como já discutido, Collections.unmodifiableList é apenas um wrapper; se a coleção original for alterada, o wrapper também mudará. Já List.copyOf cria uma cópia realmente independente.

List<String> base = new ArrayList<>(List.of("a", "b"));
List<String> wrap = Collections.unmodifiableList(base);
List<String> copy = List.copyOf(base);

base.add("c");
System.out.println(wrap); // [a, b, c] — mudou!
System.out.println(copy); // [a, b] — permaneceu o mesmo!

NullPointerException

Os métodos de fábrica (List.of, Set.of, Map.of) não permitem adicionar null:

List<String> bad = List.of("a", null); // Lançará NullPointerException já na criação!

8. Comparação das abordagens: prós e contras

Coleções mutáveis

A principal virtude das coleções mutáveis é a flexibilidade. Você pode adicionar e remover elementos em tempo real, reestruturando a coleção conforme a lógica do programa evolui. Essa abordagem é especialmente útil quando é preciso montar rapidamente uma estrutura temporária ou mudar algo dinamicamente.

Mas a conveniência tem seu preço. Uma coleção mutável sempre carrega o risco de interferência acidental: alguém no código pode alterar os dados sem querer, levando a erros difíceis de detectar. Em programas multithread a situação é ainda pior — alterações paralelas facilmente levam a condições de corrida e falhas inesperadas. Controlar o ciclo de vida desses dados também é mais difícil: é preciso lembrar constantemente quem e quando pode modificar a coleção.

Coleções imutáveis

Com coleções imutáveis, trabalha-se com mais tranquilidade. Você sabe com certeza: ninguém vai “mexer” nelas pelas suas costas. Isso torna o código mais seguro, mais fácil de depurar e testar, e as coleções podem ser compartilhadas entre threads sem bloqueios adicionais.

A desvantagem é que às vezes é preciso abrir mão de desempenho. Se for necessário adicionar um novo elemento, é preciso criar uma nova cópia da coleção, o que implica custos adicionais de memória e tempo. Além disso, montar estruturas grandes e complexas diretamente em forma imutável pode ser inconveniente. Normalmente constrói-se em uma coleção mutável temporária e, quando tudo está pronto, “congela-se”.

9. Erros comuns

Erro nº 1: Devolver uma coleção mutável para o exterior. Se você retorna de um método um ArrayList comum, qualquer código externo pode adicionar ou remover elementos. Isso pode levar a bugs muito difíceis de rastrear.

Erro nº 2: Usar um wrapper em vez de uma cópia. Se você usa Collections.unmodifiableList, mas a coleção original é alterada em outro lugar, a “imutabilidade” é uma ilusão.

Erro nº 3: Objetos mutáveis dentro de coleções imutáveis. Mesmo que a coleção em si seja imutável, seus elementos podem ser mutáveis. Isso pode levar a mudanças de estado inesperadas.

Erro nº 4: Tentar adicionar null a uma coleção criada por métodos de fábrica. Ao contrário das coleções mais antigas, os novos métodos de fábrica não permitem adicionar null — você receberá NullPointerException.

Erro nº 5: Esperar uma implementação específica. Coleções criadas via List.of ou Set.of não garantem o tipo de implementação (não é necessariamente ArrayList ou HashSet). Não conte com isso.

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