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.
- 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,QueueeMap, 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ãoArrayList<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 oimport, 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 eralength, 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 real | Como organiza | No Java |
|---|---|---|
| A lista de chamada | Em 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 alunos | Cada cidade aparece uma vez só, não importa quantos alunos moram nela. | Set (conjunto) |
| A fila da cantina | Quem chega primeiro é atendido primeiro. | Queue (fila) |
| O diário de notas | Cada 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:
offerpõe no fim da fila epolltira do começo. A Ana foi a primeira a entrar e a primeira a sair. - Map: cada
putliga 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: oHashMapnã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:
Collectioné o "avô" de listas, conjuntos e filas. Tudo o que éCollectiontemadd,remove,size,isEmpty,containse pode ser percorrido com for-each.Mapfica de fora da árvore deCollection. Ele guarda pares (chave e valor), não elementos soltos, então tem métodos próprios:put,get,keySet…LinkedListaparece duas vezes: ela é ao mesmo tempo uma lista e uma fila de duas pontas. Uma classe pode implementar várias interfaces.
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ção | Interface | Ordem dos elementos | Repetidos? | Ponto forte | Aula |
|---|---|---|---|---|---|
ArrayList | List | A de inserção | Sim | Acesso rápido por posição (get(5)). A lista padrão. | ArrayList |
LinkedList | List, Deque | A de inserção | Sim | Inserir e remover nas pontas. | LinkedList |
ArrayDeque | Deque | A da fila | Sim | Fila e pilha rápidas. | LinkedList |
HashSet | Set | Nenhuma garantida | Não | Saber muito rápido se algo está no conjunto. | HashSet |
LinkedHashSet | Set | A de inserção | Não | Sem repetidos, mas lembra a ordem. | HashSet |
TreeSet | Set | Ordenada (A→Z, 1→9) | Não | Sempre em ordem; acha o menor, o maior, "o próximo". | TreeMap e TreeSet |
HashMap | Map | Nenhuma garantida | Chaves não | Buscar um valor pela chave muito rápido. | HashMap |
LinkedHashMap | Map | A de inserção | Chaves não | Como o HashMap, mas lembra a ordem. | HashMap |
TreeMap | Map | Chaves ordenadas | Chaves não | Mapa 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… | Use | Exemplo da Ana |
|---|---|---|
| de uma sequência de coisas, em ordem, que pode crescer | ArrayList | Os alunos na ordem da chamada. |
| de buscar um valor por um nome (ou código) | HashMap | A nota de cada aluno; o preço de cada produto. |
| do mesmo, mas na ordem em que foi colocado | LinkedHashMap | Um cardápio que deve sair na ordem em que foi escrito. |
| do mesmo, mas com as chaves em ordem alfabética/numérica | TreeMap | O diário de notas em ordem alfabética. |
| de guardar coisas sem repetição ou saber "já vi isso?" | HashSet | As cidades diferentes dos alunos. |
| sem repetição e em ordem alfabética/numérica | TreeSet | As matérias da escola, de A a Z. |
| de uma fila (primeiro a chegar, primeiro a sair) | ArrayDeque | A fila de atendimento da secretaria. |
| de uma pilha (o último a entrar sai primeiro) | ArrayDeque | O "desfazer" de um editor de texto. |
| de uma lista fixa, que não vai mudar | List.of(…) | Os dias da semana. |
| de muitos números e o tamanho é conhecido e fixo | um array (int[]) | As 12 temperaturas médias do ano. |
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
LinkedListfor melhor, você troca onewe o resto do programa continua igual, porque ele só usa o que todaListtem. - Métodos que recebem
Listaceitam qualquer lista:ArrayList,LinkedList, a lista imutável doList.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);
}
}
| Linha | O 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).
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
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.
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.
$ 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 | É… | Guarda | Métodos típicos |
|---|---|---|---|
Collection<E> | Interface | Um grupo de elementos | add, remove, size, isEmpty, contains, clear |
List<E> | Interface | Sequência com posições, aceita repetidos | get(i), set(i, x), add(i, x), indexOf, sort |
Set<E> | Interface | Elementos sem repetição | add (devolve false se já existe), contains |
Queue<E> | Interface | Fila | offer, poll, peek |
Deque<E> | Interface | Fila de duas pontas / pilha | addFirst, addLast, push, pop |
Map<K, V> | Interface | Pares chave → valor | put, get, containsKey, keySet, values |
ArrayList, LinkedList | Classes | Implementam List | — |
HashSet, LinkedHashSet, TreeSet | Classes | Implementam Set | — |
ArrayDeque | Classe | Implementa Deque | — |
HashMap, LinkedHashMap, TreeMap | Classes | Implementam Map | — |
List.of, Set.of, Map.of | Métodos de fábrica | Coleçõ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:
2. Programar para a interface
Complete o tipo da variável do jeito recomendado:
3. Quantos tem agora?
Complete com o método que diz quantos elementos a lista tem:
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 comimport java.util.*;ou uma por uma. List= ordem e repetidos;Set= sem repetidos;Queue/Deque= fila e pilha;Map= chave → valor (e não é umaCollection).- Interfaces dizem o que se faz; implementações (
ArrayList,HashSet,HashMap…) dizem como. HashSeteHashMapnão garantem ordem; as versõesLinked…lembram a ordem de inserção; asTree…ordenam.- Coleções guardam objetos: use
Integer,Double… no lugar deint,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.