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.

Você vai aprender
  • O que é uma lista encadeada e como ela difere da ArrayList.
  • addFirst, addLast, getFirst, getLast, removeFirst, removeLast.
  • A LinkedList como fila (Queue): offer, poll, peek.
  • ArrayList × LinkedList: qual escolher (spoiler: quase sempre ArrayList).
  • A ArrayDeque como 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ão List, de propósito: os métodos addFirst, removeFirst… são da LinkedList (vêm da interface Deque, que você vai ver adiante).
addFirst(x) / addLast(x)
Engata no começo / no fim. addLast é o mesmo que add.
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étodoTraduçãoO que fazCom a fila vazia
offer(x)oferecerEntra no fim da fila.—
peek()espiarMostra 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çãoLança exceção se falharDevolve um valor especial
Entrar no fimadd(x)offer(x) (devolve false)
Tirar o primeiroremove()poll() (devolve null)
Ver o primeiroelement()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çãoArrayList (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 fimRápido (de vez em quando copia o array interno, mas na média é rápido).Rápido.
addFirst / removeFirstLento: todos andam uma casa.Rápido: só muda os engates.
add(i, x) / remove(i) no meioLento: arrasta metade da lista (em média).Lento também: precisa achar a posição i, contando nós.
contains / indexOfLento: olha um por um.Lento: olha um por um.
MemóriaPouca: 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.

Regra prática: quase sempre 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, uma ArrayDeque.
push(x)
"Empurrar": põe x no 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…DeclareMé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 ladosDeque<T> d = new ArrayDeque<>();offerFirst/Last, pollFirst/Last, peekFirst/Last
Fila que precisa aceitar null, ou que também precisa de get(i)LinkedListtodos acima + os de List
Fila em que sai primeiro o "menor" (por prioridade)PriorityQueueoffer, poll (sai o menor)
E a classe 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().

"cannot find symbol … method addFirst" numa variável 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étodoOndeO que fazSe vazia
addFirst(x) / addLast(x)LinkedList, DequePõe no começo / fim—
getFirst() / getLast()LinkedList, Deque, List (21+)Lê a pontaNoSuchElementException
removeFirst() / removeLast()LinkedList, DequeTira a ponta e devolveNoSuchElementException
offer(x)QueueEntra no fim—
poll()QueueTira o primeironull
peek()Queue, DequeOlha o primeiro / toponull
push(x)DequePõe no topo da pilha—
pop()DequeTira do topo da pilhaNoSuchElementException
offerFirst/Last, pollFirst/Last, peekFirst/LastDequeFila de duas pontaspoll/peek: null

Exercícios

1. Furar a fila

Complete para colocar "Dona Zélia" no começo da LinkedList chamada fila:

fila.;

2. Pilha

Complete a declaração da pilha recomendada pelo Java:

Deque<String> pilha = new ;

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 LinkedList guarda 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): offer entra no fim, poll tira do começo, peek espia; com fila vazia, devolvem null.
  • ArrayList × LinkedList: quase sempre ArrayList; get(i) numa LinkedList é muito mais lento.
  • Para fila e pilha, use ArrayDeque: push/pop/peek (pilha) ou offer/poll (fila). Evite a antiga Stack.
  • Tirar de uma estrutura vazia com pop/removeFirst dá NoSuchElementException.

Próxima aula: HashMap, para guardar pares chave → valor.