Java Coleções

Um programa de verdade quase nunca trabalha com um dado só: são os alunos de uma turma, os produtos de uma loja, as notas de uma prova. Até aqui você guardava vários valores num array. Nesta aula você vai conhecer as coleções: "caixas" prontas do Java que crescem sozinhas, evitam repetição, mantêm ordem ou ligam um nome a um valor. É a visão geral; cada coleção ganha a sua própria aula em seguida.

Você vai aprender
  • Por que o array não basta: ele tem tamanho fixo.
  • O que é uma coleção e o que o Java chama de Collections Framework.
  • As quatro famílias: List, Set, Queue e Map, e o que cada uma faz.
  • A diferença entre interface (o "contrato") e implementação (a classe que faz o trabalho).
  • Qual coleção usar em cada situação.
  • Por que se escreve List<String> x = new ArrayList<>(), e não ArrayList<String> x = ….

Todos os programas desta aula foram compilados e executados com o JDK 25 na máquina fictícia da Ana (usuária ana, computador notebook). As saídas são reais.


O problema: o array tem tamanho fixo

A Ana é professora e quer guardar os nomes dos alunos da turma. Com o que você já sabe, ela cria um array de String. Para criar um array, é preciso dizer quantas posições ele tem, e esse número nunca mais muda.

public class Main {
    public static void main(String[] args) {
        String[] alunos = new String[3];
        alunos[0] = "Ana";
        alunos[1] = "Bruno";
        alunos[2] = "Carla";
        System.out.println("Cabem " + alunos.length + " alunos.");
        alunos[3] = "Duda";
    }
}

No meio do semestre chega a Duda. A Ana tenta guardá-la na posição 3, a quarta:

$ javac Main.java
$ java Main
Cabem 3 alunos.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at Main.main(Main.java:8)

O programa compilou, mas parou com uma exceção na linha 8: "índice 3 fora dos limites para tamanho 3". O array tem as posições 0, 1 e 2, e não existe jeito de "esticar" um array. A única saída seria criar outro array, maior, e copiar tudo para ele (é o que o Arrays.copyOf da aula A Classe Arrays faz). Fazer isso à mão toda vez que chega um aluno é trabalhoso e fácil de errar.

A solução: uma coleção que cresce sozinha

O Java já vem com classes que fazem esse trabalho para você. A mais usada é a ArrayList, uma "lista que cresce". Você não diz o tamanho: só vai acrescentando.

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> alunos = new ArrayList<>();
        alunos.add("Ana");
        alunos.add("Bruno");
        alunos.add("Carla");
        System.out.println(alunos + " tamanho " + alunos.size());
        alunos.add("Duda");
        System.out.println(alunos + " tamanho " + alunos.size());
    }
}
import java.util.ArrayList;
As coleções moram no pacote java.util ("utilitários"). Sem o import, o compilador não sabe quem é ArrayList (veja Pacotes e import).
ArrayList<String>
O tipo da variável: "uma ArrayList de Strings". O que vai entre < > é o tipo dos elementos. Isso se chama generics, assunto da aula Generics.
new ArrayList<>()
Cria a lista, vazia. O <> vazio (o "diamante") quer dizer "o mesmo tipo do lado esquerdo".
alunos.add("Ana")
add = acrescentar. Põe o elemento no fim da lista.
alunos.size()
size = tamanho. Quantos elementos existem agora (no array era length, sem parênteses).
alunos + " tamanho "
Uma coleção pode ser impressa direto: ela mostra os elementos entre colchetes, separados por vírgula.
$ javac Main.java
$ java Main
[Ana, Bruno, Carla] tamanho 3
[Ana, Bruno, Carla, Duda] tamanho 4      « cresceu sozinha, sem erro

Repare em outra vantagem: o array precisava de um laço para ser mostrado (ou do Arrays.toString); a lista se imprime sozinha.

Missão: rode o programa e veja a exceção. Depois troque o array por uma ArrayList<String> (com add), acrescente a Duda e mostre a lista inteira com System.out.println(alunos). A saída deve ser [Ana, Bruno, Carla, Duda].

public class Main {
    public static void main(String[] args) {
        String[] alunos = new String[3];
        alunos[0] = "Ana";
        alunos[1] = "Bruno";
        alunos[2] = "Carla";
        alunos[3] = "Duda";
        System.out.println(alunos);
    }
}
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> alunos = new ArrayList<>();
        alunos.add("Ana");
        alunos.add("Bruno");
        alunos.add("Carla");
        alunos.add("Duda");
        System.out.println(alunos);
    }
}

O que é uma coleção

Uma coleção é um objeto que guarda outros objetos, como uma gaveta guarda papéis. O Java traz, no pacote java.util, um conjunto inteiro dessas gavetas, todas parecidas no jeito de usar. Esse conjunto se chama Collections Framework ("estrutura de coleções").

Pense nas coisas que a Ana usa na escola. Cada uma tem um jeito diferente de organizar:

Na vida realComo organizaNo Java
A lista de chamadaEm ordem, cada aluno numa posição (1º, 2º, 3º…). Pode haver dois "João".List (lista)
O conjunto de cidades de onde vêm os alunosCada cidade aparece uma vez só, não importa quantos alunos moram nela.Set (conjunto)
A fila da cantinaQuem chega primeiro é atendido primeiro.Queue (fila)
O diário de notasCada nome (a chave) aponta para uma nota (o valor). Você procura pelo nome.Map (mapa, dicionário)

Veja as quatro funcionando no mesmo programa. Não se preocupe com cada método agora: cada um vai ser explicado na aula da sua coleção.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        // List: fila de chamada, com ordem e repetição
        List<String> chamada = new ArrayList<>();
        chamada.add("Ana");
        chamada.add("Bruno");
        chamada.add("Ana");
        System.out.println("List:  " + chamada);

        // Set: sem repetição
        Set<String> cidades = new HashSet<>();
        cidades.add("Recife");
        cidades.add("Natal");
        cidades.add("Recife");
        System.out.println("Set:   " + cidades);

        // Queue: o primeiro que entra é o primeiro que sai
        Queue<String> fila = new ArrayDeque<>();
        fila.offer("Ana");
        fila.offer("Bruno");
        fila.offer("Carla");
        System.out.println("Queue: " + fila + " -> atende " + fila.poll() + ", sobra " + fila);

        // Map: chave -> valor
        Map<String, Double> notas = new HashMap<>();
        notas.put("Ana", 9.5);
        notas.put("Bruno", 7.0);
        System.out.println("Map:   " + notas + " -> nota da Ana: " + notas.get("Ana"));
    }
}
$ javac Main.java
$ java Main
List:  [Ana, Bruno, Ana]                                      « ordem de chegada, "Ana" duas vezes
Set:   [Recife, Natal]                                        « o segundo "Recife" foi ignorado
Queue: [Ana, Bruno, Carla] -> atende Ana, sobra [Bruno, Carla]  « poll() tira o primeiro da fila
Map:   {Bruno=7.0, Ana=9.5} -> nota da Ana: 9.5               « o mapa usa chaves { } e chave=valor

O que observar em cada linha:

  • List guardou tudo, na ordem em que foi acrescentado, inclusive o "Ana" repetido.
  • Set recusou a segunda "Recife": num conjunto, cada elemento existe uma vez só.
  • Queue: offer põe no fim da fila e poll tira do começo. A Ana foi a primeira a entrar e a primeira a sair.
  • Map: cada put liga uma chave ("Ana") a um valor (9.5); get("Ana") busca pelo nome. Repare que o Bruno apareceu antes da Ana, embora tenha sido colocado depois: o HashMap não guarda a ordem de chegada. Você vai entender por quê na aula HashMap.
import java.util.*;

O * importa todas as classes do pacote java.util de uma vez: List, ArrayList, Set, HashMap… É prático quando o programa usa várias coleções. A outra forma é importar uma por uma (import java.util.ArrayList;), que deixa claro o que o arquivo usa. As duas funcionam e não mudam a velocidade do programa.


O mapa das interfaces

Você notou que o programa acima escreveu, por exemplo, List<String> chamada = new ArrayList<>(): o tipo da variável é List, mas o objeto criado é um ArrayList. São duas coisas diferentes:

  • List é uma interface: um contrato que diz o que uma lista sabe fazer (add, get, size, remove…), sem dizer como.
  • ArrayList é uma implementação: uma classe que cumpre esse contrato de um jeito específico (guardando tudo num array interno que ela troca por um maior quando enche).

É como "veículo" e "ônibus": todo ônibus é um veículo, e quem só precisa "de um veículo para ir à escola" aceita ônibus, carro ou bicicleta. O Java organiza as coleções numa árvore de interfaces assim:

Iterable                  « "dá para percorrer com for-each"
 └── Collection           « um grupo de elementos: add, remove, size, contains…
      ├── List            « ordem + posição (índice) + aceita repetidos
      │     ├── ArrayList
      │     └── LinkedList
      ├── Set             « sem repetidos
      │     ├── HashSet
      │     ├── LinkedHashSet
      │     └── TreeSet   (é um SortedSet: sempre ordenado)
      └── Queue           « fila: entra no fim, sai do começo
            └── Deque     « fila de duas pontas (também serve de pilha)
                  ├── ArrayDeque
                  └── LinkedList   (é List e Deque ao mesmo tempo)

Map                       « chave → valor (NÃO é uma Collection)
 ├── HashMap
 ├── LinkedHashMap
 └── TreeMap              (é um SortedMap: chaves sempre ordenadas)

Três detalhes importantes nesse mapa:

  1. Collection é o "avô" de listas, conjuntos e filas. Tudo o que é Collection tem add, remove, size, isEmpty, contains e pode ser percorrido com for-each.
  2. Map fica de fora da árvore de Collection. Ele guarda pares (chave e valor), não elementos soltos, então tem métodos próprios: put, get, keySet…
  3. LinkedList aparece duas vezes: ela é ao mesmo tempo uma lista e uma fila de duas pontas. Uma classe pode implementar várias interfaces.
Não confunda: Collection, Collections e "coleções"

Collection (no singular) é a interface do topo da árvore. Collections (no plural) é uma classe de ferramentas, com métodos prontos como Collections.sort e Collections.max; ela tem a sua aula: A Classe Collections. E "coleções", em português, é o nome geral de tudo isso.

As implementações e o que as diferencia

Cada interface tem várias implementações. Elas fazem a mesma coisa (cumprem o mesmo contrato), mas com diferenças de ordem e de velocidade:

ImplementaçãoInterfaceOrdem dos elementosRepetidos?Ponto forteAula
ArrayListListA de inserçãoSimAcesso rápido por posição (get(5)). A lista padrão.ArrayList
LinkedListList, DequeA de inserçãoSimInserir e remover nas pontas.LinkedList
ArrayDequeDequeA da filaSimFila e pilha rápidas.LinkedList
HashSetSetNenhuma garantidaNãoSaber muito rápido se algo está no conjunto.HashSet
LinkedHashSetSetA de inserçãoNãoSem repetidos, mas lembra a ordem.HashSet
TreeSetSetOrdenada (A→Z, 1→9)NãoSempre em ordem; acha o menor, o maior, "o próximo".TreeMap e TreeSet
HashMapMapNenhuma garantidaChaves nãoBuscar um valor pela chave muito rápido.HashMap
LinkedHashMapMapA de inserçãoChaves nãoComo o HashMap, mas lembra a ordem.HashMap
TreeMapMapChaves ordenadasChaves nãoMapa sempre em ordem de chave.TreeMap e TreeSet

Os nomes seguem um padrão que ajuda a lembrar: a primeira parte diz como a coleção guarda os dados (Array = num array, Linked = elos encadeados, Hash = por um número de espalhamento, Tree = numa árvore ordenada) e a segunda parte diz o que ela é (List, Set, Map, Deque).


Qual coleção usar?

Na prática, quatro classes resolvem quase tudo: ArrayList, HashMap, HashSet e ArrayDeque. Use a tabela fazendo a pergunta da primeira coluna:

Você precisa…UseExemplo da Ana
de uma sequência de coisas, em ordem, que pode crescerArrayListOs alunos na ordem da chamada.
de buscar um valor por um nome (ou código)HashMapA nota de cada aluno; o preço de cada produto.
do mesmo, mas na ordem em que foi colocadoLinkedHashMapUm cardápio que deve sair na ordem em que foi escrito.
do mesmo, mas com as chaves em ordem alfabética/numéricaTreeMapO diário de notas em ordem alfabética.
de guardar coisas sem repetição ou saber "já vi isso?"HashSetAs cidades diferentes dos alunos.
sem repetição e em ordem alfabética/numéricaTreeSetAs matérias da escola, de A a Z.
de uma fila (primeiro a chegar, primeiro a sair)ArrayDequeA fila de atendimento da secretaria.
de uma pilha (o último a entrar sai primeiro)ArrayDequeO "desfazer" de um editor de texto.
de uma lista fixa, que não vai mudarList.of(…)Os dias da semana.
de muitos números e o tamanho é conhecido e fixoum array (int[])As 12 temperaturas médias do ano.
Na dúvida, comece com ArrayList ou HashMap

Se você precisa "de vários", é ArrayList. Se precisa "procurar por nome", é HashMap. Só troque quando aparecer uma necessidade concreta (sem repetidos, em ordem, fila). E, como você vai ver na próxima seção, trocar depois é fácil.

Coleções guardam objetos, não primitivos

Uma coleção só guarda objetos. Por isso não existe List<int> nem Map<String, double>: para números, use as classes "embrulho" Integer, Double, Boolean, Character… (as wrappers). O Java converte int ↔ Integer sozinho quando você faz add(10) ou int x = lista.get(0); isso se chama autoboxing. Foi o que o programa do mapa fez com Map<String, Double> e notas.put("Ana", 9.5). O erro de tentar List<int> está na aula Generics.


Programar para a interface

Agora a regra que você vai ver em todo código Java profissional. Compare as duas formas de criar a mesma lista:

ArrayList<String> frutas = new ArrayList<>();   // tipo da variável = a classe
List<String> frutas = new ArrayList<>();        // tipo da variável = a interface (preferida)

As duas funcionam. A segunda é a recomendada: a variável é declarada com o tipo mais geral que serve (a interface List), e só o new diz qual implementação foi escolhida. Isso se chama programar para a interface. As vantagens:

  • Trocar a implementação muda uma linha só. Se um dia a LinkedList for melhor, você troca o new e o resto do programa continua igual, porque ele só usa o que toda List tem.
  • Métodos que recebem List aceitam qualquer lista: ArrayList, LinkedList, a lista imutável do List.of…
  • O código diz o que importa. Quem lê List<String> entende "uma sequência de textos", sem se distrair com detalhes de como ela é guardada.

Veja as duas vantagens no mesmo programa:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> frutas = new ArrayList<>();
        frutas.add("maçã");
        frutas.add("banana");
        System.out.println(frutas.getClass().getSimpleName() + " " + frutas);

        frutas = new LinkedList<>(frutas);
        frutas.add("caju");
        System.out.println(frutas.getClass().getSimpleName() + " " + frutas);
        mostrar(frutas);
        mostrar(List.of("uva", "kiwi"));
    }

    static void mostrar(List<String> lista) {
        System.out.println("A lista tem " + lista.size() + " itens: " + lista);
    }
}
LinhaO que faz
List<String> frutas = new ArrayList<>();A variável é do tipo List; o objeto é um ArrayList.
frutas.getClass().getSimpleName()Pergunta ao objeto "de que classe você é?" (método herdado de Object). Só para você ver o que está dentro da variável.
frutas = new LinkedList<>(frutas);Cria uma LinkedList com uma cópia dos elementos e guarda na mesma variável. Funciona porque LinkedList também é uma List.
static void mostrar(List<String> lista)Um método que aceita qualquer List de Strings.
mostrar(List.of("uva", "kiwi"))List.of cria uma lista pronta (e imutável). O mesmo método serve.
$ javac Main.java
$ java Main
ArrayList [maçã, banana]
LinkedList [maçã, banana, caju]          « mesma variável, outra implementação
A lista tem 3 itens: [maçã, banana, caju]
A lista tem 2 itens: [uva, kiwi]         « o método aceitou outro tipo de lista

Se o método fosse mostrar(ArrayList<String> lista), as duas últimas chamadas não compilariam: uma LinkedList não é uma ArrayList. Por isso, em parâmetros, prefira sempre a interface (List, Set, Map) ou até a mais geral de todas que sirva (Collection).

E o var?

Com var frutas = new ArrayList<String>(); (veja var), o tipo da variável vira ArrayList<String>, a classe. Para variáveis locais curtas isso é aceitável, mas em atributos, parâmetros e retornos de métodos use a interface.

Brinque com o código: troque new ArrayList<>() por new LinkedList<>() e rode de novo. O resto do programa precisa mudar? Depois troque HashSet por TreeSet e veja a ordem das cidades mudar.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> chamada = new ArrayList<>();
        chamada.add("Carla");
        chamada.add("Ana");
        chamada.add("Bruno");
        System.out.println(chamada.getClass().getSimpleName() + " " + chamada);

        Set<String> cidades = new HashSet<>();
        cidades.add("Recife");
        cidades.add("Natal");
        cidades.add("Olinda");
        cidades.add("Recife");
        System.out.println(cidades.getClass().getSimpleName() + " " + cidades);
    }
}

Quando dá errado

"cannot find symbol … class ArrayList": esqueceu o import

Este código tem um erro de propósito.

public class Main {
    public static void main(String[] args) {
        ArrayList<String> alunos = new ArrayList<>();
        alunos.add("Ana");
    }
}
$ javac Main.java
Main.java:3: error: cannot find symbol
        ArrayList<String> alunos = new ArrayList<>();
        ^
  symbol:   class ArrayList
  location: class Main
Main.java:3: error: cannot find symbol
        ArrayList<String> alunos = new ArrayList<>();
                                       ^
  symbol:   class ArrayList
  location: class Main
2 errors

O que quer dizer: "não encontrei o símbolo: a classe ArrayList". São dois erros porque o nome aparece duas vezes na linha (no tipo e no new). Diferente de String e Math, que moram em java.lang e são importadas automaticamente, as coleções moram em java.util.

Como consertar: acrescente import java.util.ArrayList; (ou import java.util.*;) antes de public class Main.

"List is abstract; cannot be instantiated": tentou criar a interface

Este código tem um erro de propósito.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> alunos = new List<>();
    }
}
$ javac Main.java
Main.java:5: error: List is abstract; cannot be instantiated
        List<String> alunos = new List<>();
                              ^
1 error

O que quer dizer: "List é abstrata; não pode ser instanciada (criada)". List é só o contrato: ela diz o que uma lista faz, mas não tem o código que faz. É como pedir "um veículo" na loja: você precisa escolher qual.

Como consertar: do lado do new, use uma implementação: new ArrayList<>() ou new LinkedList<>(). Do lado esquerdo, a interface continua certa.

"ArrayIndexOutOfBoundsException": o array encheu
$ java Main
Cabem 3 alunos.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at Main.main(Main.java:8)

O que quer dizer: você tentou usar uma posição que o array não tem. Se a quantidade de elementos pode mudar durante o programa, um array é a ferramenta errada.

Como consertar: troque o array por uma ArrayList, como no começo da aula.


Referência

TipoÉ…GuardaMétodos típicos
Collection<E>InterfaceUm grupo de elementosadd, remove, size, isEmpty, contains, clear
List<E>InterfaceSequência com posições, aceita repetidosget(i), set(i, x), add(i, x), indexOf, sort
Set<E>InterfaceElementos sem repetiçãoadd (devolve false se já existe), contains
Queue<E>InterfaceFilaoffer, poll, peek
Deque<E>InterfaceFila de duas pontas / pilhaaddFirst, addLast, push, pop
Map<K, V>InterfacePares chave → valorput, get, containsKey, keySet, values
ArrayList, LinkedListClassesImplementam List—
HashSet, LinkedHashSet, TreeSetClassesImplementam Set—
ArrayDequeClasseImplementa Deque—
HashMap, LinkedHashMap, TreeMapClassesImplementam Map—
List.of, Set.of, Map.ofMétodos de fábricaColeções prontas e imutáveis—

Para uma cola com os métodos de todas as coleções, veja Cola de Coleções.


Exercícios

1. O pacote certo

Complete o import que traz todas as coleções:

import

2. Programar para a interface

Complete o tipo da variável do jeito recomendado:

nomes = new ArrayList<>();

3. Quantos tem agora?

Complete com o método que diz quantos elementos a lista tem:

System.out.println(nomes.);

4. A Ana quer guardar o e-mail de cada aluno e encontrar o e-mail pelo nome. Qual coleção?

5. Num Set<String> vazio, a Ana faz add("Natal") três vezes. Quanto vale size()?

6. Qual destes não é uma Collection?

7. Por que new List<>() não compila?

Missão final: a Ana anotou a cidade de cada aluno no array anotadas. Crie uma List<String> com todas as cidades e um Set<String> com as cidades diferentes (programando para a interface), preencha os dois com um for-each e mostre Alunos: 5 e Cidades diferentes: 3 usando size().

import java.util.*;

public class Main {
    public static void main(String[] args) {
        String[] anotadas = {"Recife", "Natal", "Recife", "Olinda", "Natal"};

        // crie a List e o Set aqui

        for (String cidade : anotadas) {
            // acrescente a cidade nas duas coleções
        }

        // mostre os tamanhos
    }
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        String[] anotadas = {"Recife", "Natal", "Recife", "Olinda", "Natal"};

        List<String> todas = new ArrayList<>();
        Set<String> diferentes = new HashSet<>();
        for (String cidade : anotadas) {
            todas.add(cidade);
            diferentes.add(cidade);
        }
        System.out.println("Alunos: " + todas.size());
        System.out.println("Cidades diferentes: " + diferentes.size());
    }
}

Resumo

  • Um array tem tamanho fixo; passar do fim dá ArrayIndexOutOfBoundsException. Coleções crescem sozinhas.
  • As coleções moram em java.util: importe com import java.util.*; ou uma por uma.
  • List = ordem e repetidos; Set = sem repetidos; Queue/Deque = fila e pilha; Map = chave → valor (e não é uma Collection).
  • Interfaces dizem o que se faz; implementações (ArrayList, HashSet, HashMap…) dizem como.
  • HashSet e HashMap não garantem ordem; as versões Linked… lembram a ordem de inserção; as Tree… ordenam.
  • Coleções guardam objetos: use Integer, Double… no lugar de int, double.
  • Programe para a interface: List<String> x = new ArrayList<>();. Trocar a implementação muda uma linha.

Próxima aula: a coleção mais usada do Java, a ArrayList.