Java LinkedList
A LinkedList é a "outra" lista do Java. Ela faz tudo o que a ArrayList faz, mas guarda os elementos de um jeito diferente: como vagões de um trem, cada um engatado no próximo. Isso a deixa boa para mexer nas pontas (começo e fim) e, por isso, ela também serve de fila. Nesta aula você vai vê-la como lista e como fila, entender quando ela ganha (e quando perde) da ArrayList, e conhecer a ArrayDeque, a melhor escolha para filas e pilhas.
- O que é uma lista encadeada e como ela difere da
ArrayList. addFirst,addLast,getFirst,getLast,removeFirst,removeLast.- A
LinkedListcomo fila (Queue):offer,poll,peek. - ArrayList × LinkedList: qual escolher (spoiler: quase sempre
ArrayList). - A
ArrayDequecomo pilha (push,pop) e como fila. - A exceção
NoSuchElementException.
A ideia: um trem de vagões
A ArrayList guarda os elementos lado a lado, numa fileira de cadeiras numeradas. Para achar a cadeira 500, basta ir direto a ela. Mas, para encaixar alguém na cadeira 0, todo mundo precisa levantar e andar uma cadeira para o lado.
A LinkedList guarda cada elemento num "vagão" (o nome técnico é nó) que sabe quem é o vagão da frente e o de trás. Daí o nome: linked = ligada, encadeada.
ArrayList: [ Ana | Bruno | Carla | Duda ] « fileira numerada: 0, 1, 2, 3
LinkedList: Ana ⇄ Bruno ⇄ Carla ⇄ Duda « cada vagão conhece o vizinho
↑ primeiro ↑ último
As consequências:
- Engatar ou soltar um vagão na ponta é instantâneo: basta mudar dois engates. Ninguém precisa andar.
- Achar o vagão número 500 é lento: não há número pintado nos vagões. É preciso começar da ponta e contar um por um.
A LinkedList como lista
Tudo o que você aprendeu de List funciona igual: add, get, set, remove, size, contains… Além disso, ela tem métodos próprios para as pontas:
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> trem = new LinkedList<>();
trem.add("vagão 1");
trem.add("vagão 2");
System.out.println("Início: " + trem);
trem.addFirst("locomotiva");
trem.addLast("vagão 3");
System.out.println("addFirst/Last: " + trem);
System.out.println("getFirst(): " + trem.getFirst());
System.out.println("getLast(): " + trem.getLast());
System.out.println("get(2): " + trem.get(2));
String saiu1 = trem.removeFirst();
String saiu2 = trem.removeLast();
System.out.println("removeFirst tirou " + saiu1 + ", removeLast tirou " + saiu2);
System.out.println("Sobrou: " + trem);
}
}
- LinkedList<String> trem
- Aqui a variável é do tipo
LinkedList, e nãoList, de propósito: os métodosaddFirst,removeFirst… são daLinkedList(vêm da interfaceDeque, que você vai ver adiante). - addFirst(x) / addLast(x)
- Engata no começo / no fim.
addLasté o mesmo queadd. - getFirst() / getLast()
- Lê o primeiro / o último, sem tirar.
- removeFirst() / removeLast()
- Tira o primeiro / o último e devolve quem saiu.
$ javac Main.java $ java Main Início: [vagão 1, vagão 2] addFirst/Last: [locomotiva, vagão 1, vagão 2, vagão 3] getFirst(): locomotiva getLast(): vagão 3 get(2): vagão 2 « funciona, mas conta vagão por vagão removeFirst tirou locomotiva, removeLast tirou vagão 3 Sobrou: [vagão 1, vagão 2]
A LinkedList se imprime igual à ArrayList, entre colchetes. Olhando a saída, não dá para saber qual das duas é: a diferença está só em como os dados são guardados.
getFirst e companhia também existem na ArrayList Java 21+
Desde o Java 21, toda List ganhou getFirst, getLast, addFirst, addLast, removeFirst e removeLast (a novidade se chama sequenced collections). Na ArrayList, porém, addFirst e removeFirst continuam tendo que "arrastar" todo mundo. Em versões anteriores ao 21, só a LinkedList (e a ArrayDeque) têm esses métodos.
A LinkedList como fila (Queue)
Uma fila funciona como a da cantina: quem chega entra no fim; quem é atendido sai do começo. Em inglês isso se chama FIFO, first in, first out ("o primeiro a entrar é o primeiro a sair"). A interface do Java para filas é a Queue (pronuncia-se "kiú", fila), e a LinkedList é uma das classes que a implementam.
import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> fila = new LinkedList<>();
fila.offer("Ana");
fila.offer("Bruno");
fila.offer("Carla");
System.out.println("Fila: " + fila);
System.out.println("Próxima (peek): " + fila.peek());
while (!fila.isEmpty()) {
String pessoa = fila.poll();
System.out.println("Atendendo " + pessoa + " | ainda esperam: " + fila);
}
System.out.println("poll() na fila vazia: " + fila.poll());
System.out.println("peek() na fila vazia: " + fila.peek());
}
}
| Método | Tradução | O que faz | Com a fila vazia |
|---|---|---|---|
offer(x) | oferecer | Entra no fim da fila. | — |
peek() | espiar | Mostra quem é o próximo, sem tirar. | devolve null |
poll() | "chamar a vez" | Tira o primeiro da fila e devolve. | devolve null |
$ javac Main.java $ java Main Fila: [Ana, Bruno, Carla] Próxima (peek): Ana Atendendo Ana | ainda esperam: [Bruno, Carla] Atendendo Bruno | ainda esperam: [Carla] Atendendo Carla | ainda esperam: [] poll() na fila vazia: null « fila vazia: null, sem exceção peek() na fila vazia: null
O laço while (!fila.isEmpty()) se lê "enquanto a fila não estiver vazia": atende um por vez até acabar. Repare que a variável foi declarada como Queue: assim ela só mostra os métodos de fila, e ninguém "fura a fila" com um add(0, …) por engano.
Cada operação tem duas versões
A Queue tem dois métodos para cada operação. A diferença está no que acontece quando não dá certo (por exemplo, fila vazia):
| Operação | Lança exceção se falhar | Devolve um valor especial |
|---|---|---|
| Entrar no fim | add(x) | offer(x) (devolve false) |
| Tirar o primeiro | remove() | poll() (devolve null) |
| Ver o primeiro | element() | peek() (devolve null) |
Para filas, prefira a coluna da direita (offer, poll, peek): uma fila vazia é uma situação normal, não um erro.
Missão: a Duda chegou depois da Carla. Coloque-a no fim da fila com offer e escreva um laço que atenda todo mundo com poll(), mostrando Atendendo NOME para cada um, na ordem de chegada.
import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> fila = new LinkedList<>();
fila.offer("Ana");
fila.offer("Bruno");
fila.offer("Carla");
// a Duda chegou
// atenda todo mundo
}
}
import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> fila = new LinkedList<>();
fila.offer("Ana");
fila.offer("Bruno");
fila.offer("Carla");
fila.offer("Duda");
while (!fila.isEmpty()) {
System.out.println("Atendendo " + fila.poll());
}
}
}
ArrayList × LinkedList
As duas são List. Qual escolher? Veja o que cada operação custa em cada uma:
| Operação | ArrayList (fileira) | LinkedList (trem) |
|---|---|---|
get(i) / set(i, x) | Rápido: vai direto à posição. | Lento: conta nó por nó desde a ponta mais próxima. |
add(x) no fim | Rápido (de vez em quando copia o array interno, mas na média é rápido). | Rápido. |
addFirst / removeFirst | Lento: todos andam uma casa. | Rápido: só muda os engates. |
add(i, x) / remove(i) no meio | Lento: arrasta metade da lista (em média). | Lento também: precisa achar a posição i, contando nós. |
contains / indexOf | Lento: olha um por um. | Lento: olha um por um. |
| Memória | Pouca: só os elementos. | Mais: cada nó guarda o elemento e dois engates. |
Veja a diferença do get(i) na prática. Este programa enche cada lista com 20 mil números e depois lê todos com get(i), medindo o tempo com System.nanoTime() (o relógio do computador, em nanossegundos):
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Main {
static long medir(List<Integer> lista) {
for (int i = 0; i < 20_000; i++) {
lista.add(i);
}
long inicio = System.nanoTime();
long soma = 0;
for (int i = 0; i < lista.size(); i++) {
soma += lista.get(i);
}
return (System.nanoTime() - inicio) / 1_000_000;
}
public static void main(String[] args) {
System.out.println("ArrayList, 20 mil get(i): " + medir(new ArrayList<>()) + " ms");
System.out.println("LinkedList, 20 mil get(i): " + medir(new LinkedList<>()) + " ms");
}
}
$ javac Main.java $ java Main ArrayList, 20 mil get(i): 2 ms LinkedList, 20 mil get(i): 247 ms « mais de 100 vezes mais lento
Os números mudam de computador para computador e de uma execução para outra, mas a diferença é sempre enorme. Repare que o método medir recebe List<Integer>: graças a programar para a interface, o mesmo método serve para as duas.
ArrayList
Na maioria dos programas, o que mais se faz com uma lista é acrescentar no fim, ler por posição e percorrer, e nisso a ArrayList ganha. Os computadores modernos também são muito rápidos para copiar blocos de memória contínuos, o que diminui a desvantagem dela no meio da lista. Se você precisa de uma fila ou pilha, a melhor escolha é a ArrayDeque, logo abaixo. A LinkedList fica para quando você precisa, ao mesmo tempo, de uma List e de uma fila de duas pontas, ou de guardar null numa fila.
ArrayDeque: pilha e fila rápidas
Deque (pronuncia-se "déc") vem de double-ended queue, "fila de duas pontas": dá para entrar e sair pelos dois lados. A ArrayDeque implementa isso com um array "circular", sem nós: é mais rápida e gasta menos memória do que a LinkedList para esse uso. Com ela você monta tanto uma fila quanto uma pilha.
Pilha: o último a entrar é o primeiro a sair
Pense numa pilha de pratos: você põe um prato em cima e, quando precisa, tira o de cima. Em inglês, LIFO (last in, first out). O exemplo clássico é o "desfazer" (Ctrl+Z) de um editor: ele desfaz primeiro a última coisa que você fez.
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<String> desfazer = new ArrayDeque<>();
desfazer.push("digitou 'Olá'");
desfazer.push("apagou uma palavra");
desfazer.push("colou um texto");
System.out.println("Pilha: " + desfazer);
System.out.println("Topo (peek): " + desfazer.peek());
System.out.println("Ctrl+Z desfaz: " + desfazer.pop());
System.out.println("Ctrl+Z desfaz: " + desfazer.pop());
System.out.println("Pilha: " + desfazer);
}
}
- Deque<String> desfazer = new ArrayDeque<>();
- A variável é a interface
Deque; o objeto, umaArrayDeque. - push(x)
- "Empurrar": põe
xno topo da pilha (na prática, no começo do deque). - peek()
- Olha o topo sem tirar.
- pop()
- "Estourar": tira o topo e devolve.
$ javac Main.java $ java Main Pilha: [colou um texto, apagou uma palavra, digitou 'Olá'] « o topo aparece primeiro Topo (peek): colou um texto Ctrl+Z desfaz: colou um texto Ctrl+Z desfaz: apagou uma palavra Pilha: [digitou 'Olá']
A ordem impressa é a do topo para o fundo: o último que entrou (colou um texto) aparece primeiro e é o primeiro a sair.
Fila com as duas pontas
A mesma ArrayDeque serve de fila. Com os métodos …First/…Last, você escolhe a ponta:
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<String> fila = new ArrayDeque<>();
fila.offerLast("Ana");
fila.offerLast("Bruno");
fila.offerFirst("Dona Zélia (prioridade)");
System.out.println("Fila: " + fila);
System.out.println("Atende: " + fila.pollFirst());
System.out.println("Desistiu do fim: " + fila.pollLast());
System.out.println("Fila: " + fila);
}
}
$ javac Main.java $ java Main Fila: [Dona Zélia (prioridade), Ana, Bruno] « offerFirst furou a fila (de propósito) Atende: Dona Zélia (prioridade) Desistiu do fim: Bruno Fila: [Ana]
Qual usar para fila e pilha?
| Você quer… | Declare | Métodos |
|---|---|---|
| Uma fila simples (FIFO) | Queue<T> f = new ArrayDeque<>(); | offer, poll, peek |
| Uma pilha (LIFO) | Deque<T> p = new ArrayDeque<>(); | push, pop, peek |
| Entrar e sair pelos dois lados | Deque<T> d = new ArrayDeque<>(); | offerFirst/Last, pollFirst/Last, peekFirst/Last |
Fila que precisa aceitar null, ou que também precisa de get(i) | LinkedList | todos acima + os de List |
| Fila em que sai primeiro o "menor" (por prioridade) | PriorityQueue | offer, poll (sai o menor) |
Stack?
Em código antigo você vai encontrar Stack<T> ("pilha"), com push e pop. Ela ainda funciona, mas é uma classe dos anos 1990, mais lenta (é sincronizada, uma proteção para threads que custa tempo). A própria documentação do Java recomenda usar Deque/ArrayDeque no lugar. Outra diferença: a ArrayDeque não aceita null: push(null) termina em java.lang.NullPointerException, lançada de dentro de ArrayDeque.addFirst.
Brinque com o código: troque push por offerLast e pop por pollFirst. A pilha virou fila? O que muda na ordem de saída?
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<Integer> d = new ArrayDeque<>();
d.push(1);
d.push(2);
d.push(3);
System.out.println("Deque: " + d);
while (!d.isEmpty()) {
System.out.println("saiu " + d.pop());
}
}
}
Quando dá errado
NoSuchElementException: tirou de uma pilha vazia
Este código tem um erro de propósito.
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<String> pilha = new ArrayDeque<>();
pilha.push("único");
System.out.println(pilha.pop());
System.out.println(pilha.pop());
}
}
$ javac Main.java $ java Main único Exception in thread "main" java.util.NoSuchElementException at java.base/java.util.ArrayDeque.removeFirst(ArrayDeque.java:360) at java.base/java.util.ArrayDeque.pop(ArrayDeque.java:591) at Main.main(Main.java:9)
O que quer dizer: "não existe tal elemento". O primeiro pop tirou o único prato; o segundo tentou tirar de uma pilha vazia. Repare na pilha de chamadas: pop chama removeFirst por dentro. Na LinkedList é igual:
$ java Main Exception in thread "main" java.util.NoSuchElementException at java.base/java.util.LinkedList.removeFirst(LinkedList.java:282) at Main.main(Main.java:6)
Como consertar: confira antes com isEmpty() (while (!pilha.isEmpty())), ou use os métodos que devolvem null em vez de lançar exceção: poll(), pollFirst(), peek().
List (Java 17 ou anterior)
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> trem = new LinkedList<>();
trem.addFirst("locomotiva");
System.out.println(trem);
}
}
A opção --release 17 faz o javac do JDK 25 compilar como se fosse o Java 17:
$ javac --release 17 Main.java
Main.java:7: error: cannot find symbol
trem.addFirst("locomotiva");
^
symbol: method addFirst(String)
location: variable trem of type List<String>
1 error
$ javac Main.java && java Main
[locomotiva] « no Java 21+ compila e funciona
O que aconteceu: o compilador só deixa usar os métodos do tipo da variável (List), não os do objeto (LinkedList). Até o Java 17, List não tinha addFirst: "variável trem do tipo List<String>" não tem esse método.
Como consertar: declare a variável como Deque<String> (ou LinkedList<String>), ou use add(0, x) e remove(0).
Referência
| Método | Onde | O que faz | Se vazia |
|---|---|---|---|
addFirst(x) / addLast(x) | LinkedList, Deque | Põe no começo / fim | — |
getFirst() / getLast() | LinkedList, Deque, List (21+) | Lê a ponta | NoSuchElementException |
removeFirst() / removeLast() | LinkedList, Deque | Tira a ponta e devolve | NoSuchElementException |
offer(x) | Queue | Entra no fim | — |
poll() | Queue | Tira o primeiro | null |
peek() | Queue, Deque | Olha o primeiro / topo | null |
push(x) | Deque | Põe no topo da pilha | — |
pop() | Deque | Tira do topo da pilha | NoSuchElementException |
offerFirst/Last, pollFirst/Last, peekFirst/Last | Deque | Fila de duas pontas | poll/peek: null |
Exercícios
1. Furar a fila
Complete para colocar "Dona Zélia" no começo da LinkedList chamada fila:
2. Pilha
Complete a declaração da pilha recomendada pelo Java:
3. Numa pilha, a Ana fez push("A"), push("B"), push("C"). O que o primeiro pop() devolve?
4. O que poll() devolve numa Queue vazia?
5. A Ana vai ler milhares de vezes "o aluno da posição i" de uma lista. Qual implementação?
Missão final: use uma pilha para inverter a palavra "Java". Empilhe cada letra com push e depois desempilhe todas com pop, juntando numa String. A saída deve ser avaJ. (O toCharArray() transforma a String num array de char; veja Métodos de String.)
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
String palavra = "Java";
Deque<Character> pilha = new ArrayDeque<>();
for (char c : palavra.toCharArray()) {
// empilhe a letra
}
String invertida = "";
// desempilhe tudo, juntando em invertida
System.out.println(invertida);
}
}
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
String palavra = "Java";
Deque<Character> pilha = new ArrayDeque<>();
for (char c : palavra.toCharArray()) {
pilha.push(c);
}
String invertida = "";
while (!pilha.isEmpty()) {
invertida += pilha.pop();
}
System.out.println(invertida);
}
}
Resumo
- A
LinkedListguarda os elementos em nós encadeados: rápida nas pontas, lenta para achar uma posição. - Métodos das pontas:
addFirst,addLast,getFirst,getLast,removeFirst,removeLast. - Como fila (
Queue):offerentra no fim,polltira do começo,peekespia; com fila vazia, devolvemnull. - ArrayList × LinkedList: quase sempre
ArrayList;get(i)numaLinkedListé muito mais lento. - Para fila e pilha, use
ArrayDeque:push/pop/peek(pilha) ouoffer/poll(fila). Evite a antigaStack. - Tirar de uma estrutura vazia com
pop/removeFirstdáNoSuchElementException.
Próxima aula: HashMap, para guardar pares chave → valor.