CodeGym /Cursos /JAVA 25 SELF /List.of, Set.of, Map.of — coleções imutáveis

List.of, Set.of, Map.of — coleções imutáveis

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

1. Introdução

Coleções clássicas: flexibilidade e armadilhas

Quando você cria uma coleção com new ArrayList<>(), você obtém uma estrutura que pode ser livremente modificada: adicionar, remover, alterar elementos. Isso é conveniente quando você monta os dados “em tempo real”. Mas e se você passar essa coleção para outra classe ou método onde ela não deve ser alterada? E se você acidentalmente expuser essa coleção e alguém a modificar? É aí que começam os problemas.

Exemplo de erro clássico

import java.util.*;

public class Example {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        // Passamos a coleção "para fora"
        processNames(names);

        // Esperamos que a lista não tenha mudado...
        System.out.println(names);
    }

    public static void processNames(List<String> list) {
        // Mas alguém foi lá e removeu um elemento!
        list.remove("Bob");
    }
}

Saída:

[Alice, Charlie]

Sua coleção mudou, embora você não tivesse planejado isso. Em projetos grandes, tais “surpresas” facilmente se transformam em bugs muito desagradáveis e difíceis de rastrear.

O perigo é que o código que trabalha com a coleção pode, de repente, se deparar com mudanças imprevisíveis nos dados. Além disso, sempre há risco de perda de informação — alguém remove um elemento por engano ou o sobrescreve. E, se a coleção for modificada simultaneamente por diferentes threads, você pode acabar não só com uma ConcurrentModificationException, mas também com um problema ainda mais traiçoeiro — dados inconsistentes.

Protegendo coleções: abordagem antiga

Até o Java 9, era preciso usar métodos-invólucro como Collections.unmodifiableList(...), de que falamos no nível anterior. Eles ajudam a devolver uma coleção “congelada”. Mas esse método nem sempre é conveniente e não resolve todos os problemas (mais detalhes — na próxima aula).

2. Solução moderna: métodos de fábrica List.of, Set.of, Map.of

No Java 9 surgiram novos métodos estáticos nas interfaces de coleções: List.of, Set.of, Map.of. Eles permitem criar rápida e convenientemente uma coleção que não pode ser modificada. É como se você criasse a coleção e a concretasse imediatamente — ninguém poderá adicionar, remover ou alterar elementos.

Exemplo de criação de coleções imutáveis

import java.util.*;

public class ImmutableDemo {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");
        Set<Integer> numbers = Set.of(1, 2, 3);
        Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25, "Charlie", 28);

        System.out.println(names);
        System.out.println(numbers);
        System.out.println(ages);
    }
}

Saída:

[Alice, Bob, Charlie]
[1, 2, 3]
{Alice=30, Bob=25, Charlie=28}

Como funciona?

  • List.of(...) — cria uma lista imutável.
  • Set.of(...) — cria um conjunto imutável.
  • Map.of(...) — cria um mapa imutável (até 10 pares chave-valor; para uma quantidade maior, use Map.ofEntries(...)).

Atenção! As coleções criadas por esses métodos não aceitam alterações. Qualquer tentativa de adicionar, remover ou substituir um elemento lançará uma exceção.

3. Exemplos de uso e “armadilhas”

Exemplo: tentativa de modificar a coleção

import java.util.*;

public class ImmutableFail {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob");
        // names.add("Charlie"); // Erro em tempo de execução!
        try {
            names.add("Charlie");
        } catch (UnsupportedOperationException ex) {
            System.out.println("Não é possível adicionar elemento: " + ex.getClass().getSimpleName());
        }
    }
}

Saída:

Não é possível adicionar elemento: UnsupportedOperationException

Exemplo: tentativa de adicionar null

import java.util.*;

public class NullFail {
    public static void main(String[] args) {
        try {
            List<String> badList = List.of("Alice", null, "Bob");
        } catch (NullPointerException ex) {
            System.out.println("null não é permitido: " + ex.getClass().getSimpleName());
        }
    }
}

Saída:

null não é permitido: NullPointerException

Exemplo: duplicatas em Set.of

import java.util.*;

public class DuplicatesFail {
    public static void main(String[] args) {
        try {
            Set<String> badSet = Set.of("one", "two", "one");
        } catch (IllegalArgumentException ex) {
            System.out.println("Duplicatas não são permitidas: " + ex.getClass().getSimpleName());
        }
    }
}

Saída:

Duplicatas não são permitidas: IllegalArgumentException

Exemplo: Map.of com grande quantidade de pares

import java.util.*;

public class MapOfLarge {
    public static void main(String[] args) {
        // Map.of suporta até 10 pares chave-valor
        Map<String, Integer> map = Map.of(
            "one", 1, "two", 2, "three", 3, "four", 4, "five", 5,
            "six", 6, "seven", 7, "eight", 8, "nine", 9, "ten", 10
        );
        System.out.println(map);

        // Para uma quantidade maior, use Map.ofEntries
        Map<String, Integer> bigMap = Map.ofEntries(
            Map.entry("eleven", 11),
            Map.entry("twelve", 12),
            Map.entry("thirteen", 13)
            // ...e assim por diante
        );
        System.out.println(bigMap);
    }
}

4. Características e limitações das coleções imutáveis

Não é possível modificar.
Qualquer tentativa de adicionar, remover ou alterar um elemento resultará em UnsupportedOperationException. Até métodos que normalmente são permitidos (add, remove, set) não funcionam.

Não é possível usar null.
Se você tentar adicionar null como elemento de lista ou conjunto, ou como chave/valor em um mapa, receberá NullPointerException. Isso é feito por segurança: elementos null frequentemente causam erros em coleções.

Implementação específica não é garantida.
Você não saberá qual classe específica está por trás da coleção criada via List.of etc. Não faça instanceof ArrayList nem tente converter a coleção para algum tipo concreto.

Ordem dos elementos.
— Em List.of a ordem dos elementos é preservada (como em uma lista comum).
— Em Set.of a ordem não é garantida (na prática pode coincidir com a ordem de passagem dos argumentos, mas é melhor não contar com isso).
— Em Map.of a ordem dos pares não é garantida.

Desempenho.
Coleções criadas via métodos de fábrica geralmente funcionam mais rápido do que invólucros sobre coleções mutáveis, pois não gastam memória com funcionalidades desnecessárias.

5. Quando e por que usar coleções imutáveis

Conjuntos de dados constantes

Se você tem uma lista, conjunto ou mapa que não devem mudar durante a execução do programa, use List.of, Set.of, Map.of. Por exemplo:

private static final List<String> ROLES = List.of("USER", "ADMIN", "MODERATOR");

Agora ninguém conseguirá adicionar uma função extra a essa lista.

Retornar coleções de métodos

Se você retorna uma coleção de um método e não quer que alguém a altere externamente:

public List<String> getDefaultNames() {
    return List.of("Alice", "Bob", "Charlie");
}

Quem receber não poderá estragar seus dados.

Passagem entre camadas da aplicação

Quando você passa coleções entre diferentes partes do programa (por exemplo, entre as camadas Controller e Service em uma aplicação web), é melhor usar coleções imutáveis para que ninguém possa modificá-las “na surdina”.

Segurança e thread-safety

Coleções imutáveis são, por definição, thread-safe no que diz respeito à leitura: se ninguém pode alterá-las, é seguro usá-las a partir de múltiplas threads sem sincronização.

6. Exemplos práticos para um aplicativo genérico

Suponha que, no nosso aplicativo de estudo, haja uma lista de comandos suportados:

public class Commands {
    public static final List<String> SUPPORTED_COMMANDS = List.of(
        "help", "exit", "list", "add", "remove"
    );
}

Se você tentar fazer isto:

Commands.SUPPORTED_COMMANDS.add("hack_the_system");

Você receberá uma exceção e não conseguirá prejudicar o aplicativo.

Ou, por exemplo, se você tem um mapa com códigos de erro:

public class ErrorCodes {
    public static final Map<Integer, String> CODES = Map.of(
        404, "Not Found",
        500, "Internal Server Error",
        403, "Forbidden"
    );
}

Qualquer tentativa de adicionar um novo código — lançará uma exceção.

7. Comparação de abordagens de criação de coleções

Forma de criação Pode alterar? null é permitido? Duplicatas? Segurança de thread Exemplo
new ArrayList<>()
Sim Sim Sim Não
new ArrayList<>()
List.of(...)
Não Não Sim Sim*
List.of("a", "b")
Set.of(...)
Não Não Não Sim*
Set.of("a", "b")
Map.of(...)
Não Não Não Sim*
Map.of("a", 1, "b", 2)
Collections.unmodifiableList(...)
Não Depende da original Sim Não
Collections.unmodifiableList(list)

* — segurança de thread apenas no sentido de imutabilidade: se ninguém altera a coleção, ela pode ser lida com segurança por múltiplas threads.

8. Erros comuns ao trabalhar com List.of, Set.of, Map.of

Erro nº 1: tentativa de modificar a coleção.
Um erro muito comum — tentar adicionar ou remover um elemento de uma coleção criada via List.of, Set.of ou Map.of. Por exemplo, names.add("Dmitry") ou ages.remove("Bob"). Isso sempre leva a UnsupportedOperationException em tempo de execução.

Erro nº 2: tentativa de adicionar null.
Se você passar null acidentalmente a qualquer um dos métodos (por exemplo, List.of("Alice", null)), receberá NullPointerException. As coleções imutáveis do Java 9+ não gostam de null — e isso, na verdade, é bom.

Erro nº 3: duplicatas em Set.of ou Map.of.
Set.of("a", "b", "a") ou Map.of("x", 1, "x", 2) resultarão em IllegalArgumentException. Conjunto e mapa, por definição, não podem conter duplicatas.

Erro nº 4: esperar uma implementação específica.
Não faça assim:

List<String> list = List.of("a", "b");
if (list instanceof ArrayList) { 
    // ...
} // Isso é sempre false!

A implementação interna é ocultada — não dependa de detalhes de implementação.

Erro nº 5: tentar usar métodos de modificação da coleção.
Até métodos como clear(), set(index, value) (em listas) lançarão exceções. Lembre-se: coleções criadas via métodos de fábrica são imutáveis.

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