Interface Queue do Java
Operações de fila FIFO em Java com a interface Queue — offer, poll, peek — e suas implementações.
Queue<E> é Collection<E> mais a ideia de uma cabeça. A cabeça é o próximo elemento a ser removido; todo o resto aguarda sua vez atrás dela. A semântica padrão — e a que quase todas as implementações no JDK usam — é FIFO: primeiro a entrar, primeiro a sair. Produtores usam offer para inserir itens no final; consumidores usam poll para removê-los da cabeça. Este capítulo trata do contrato: os seis métodos, os dois estilos de tratamento de erros, a regra sobre null e qual implementação escolher em cada situação.
Duas formas de fazer cada coisa — por design
Toda operação de fila tem dois sabores: um que lança exceção em caso de falha e outro que retorna um valor especial. O Javadoc apresenta isso como uma grade 3×2:
| Operação | Lança exceção em falha | Retorna valor especial |
|---|---|---|
| Inserir | boolean add(E e) (lança IllegalStateException se cheia) | boolean offer(E e) (retorna false se cheia) |
| Remover | E remove() (lança NoSuchElementException se vazia) | E poll() (retorna null se vazia) |
| Examinar | E element() (lança NoSuchElementException se vazia) | E peek() (retorna null se vazia) |
A coluna "lança exceção" é a herdada de Collection e List — é familiar, mas inconveniente quando o caso vazio/cheio é normal (consumidor aguardando o próximo trabalho, fila limitada rejeitando um produtor em excesso). A coluna "retorna valor especial" foi adicionada para filas, permitindo escrever loops que não dependem de exceções para controle de fluxo:
String item;
while ((item = queue.poll()) != null) {
process(item);
}Use a forma de retorno por padrão. Recorra a remove/element apenas quando uma fila vazia naquele ponto seria um erro genuíno que você deseja expor como exceção.
Null e Queue não se misturam
Como poll() e peek() retornam null para indicar "vazia", permitir null como elemento real é uma receita para ambiguidade. Toda Queue de uso geral no JDK, exceto LinkedList, rejeita null — ArrayDeque, PriorityQueue, ArrayBlockingQueue, todas as filas concorrentes. LinkedList permite adicionar null, mas se você fizer isso terá quebrado o contrato: não há mais como distinguir "a cabeça é null" de "a fila está vazia".
A regra: não armazene null em uma fila. Escolha ArrayDeque em vez de LinkedList e a linguagem impõe isso por você.
FIFO é o padrão — mas não é universal
Queue não exige FIFO. A interface define uma cabeça e operações que removem dela; como a cabeça é determinada cabe à implementação. As duas implementações em java.util que não são FIFO são:
PriorityQueue— a cabeça é sempre o menor elemento (por ordem natural ou umComparator). As inserções vão onde o heap determina, não no final.- As variantes bloqueantes em
java.util.concurrent—PriorityBlockingQueue,DelayQueue, etc. — reordenam por prioridade, prazo, etc.
Todo o restante (ArrayDeque, LinkedList, ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue) é FIFO.
Escolhendo uma implementação
Para o caso single-threaded:
ArrayDeque— o padrão. Array circular, sem alocação por elemento, rápido. Use comoQueue<E>ou comoDeque<E>dependendo de precisar acessar ambas as extremidades.LinkedList— funciona, também implementaList. Escolha apenas se precisar genuinamente de ambas as interfaces no mesmo objeto. O capítulo sobre LinkedList cobre as trocas.PriorityQueue— quando você quer "menor item aguardando a seguir" em vez de FIFO.
Para o caso multi-threaded (coberto em detalhe na parte de concorrência mais adiante — apenas referências aqui):
ConcurrentLinkedQueue— FIFO sem bloqueio. Ilimitada.ArrayBlockingQueue— limitada, respaldada por array, bloqueiaputquando cheia etakequando vazia. A fila clássica produtor/consumidor.LinkedBlockingQueue— opcionalmente limitada, forma de nó encadeado da mesma.PriorityBlockingQueue—PriorityQueueconcorrente. Ilimitada.
A maioria dos códigos de aplicação usa uma das quatro primeiras. As filas bloqueantes são como você constrói pools de trabalhadores e controle de fluxo.
O que você pode chamar em qualquer Queue
Além dos seis métodos específicos de cabeça, uma Queue também é uma Collection — portanto size, isEmpty, contains, iterator, forEach, stream, clear, toArray funcionam. A ordem de iteração é a ordem em que os elementos seriam removidos por poll nas implementações FIFO (ArrayDeque, LinkedList), mas para PriorityQueue a ordem do iterador não é a ordem de prioridade — ele visita o array de heap subjacente como está. Se quiser os elementos em ordem de prioridade, você precisa esvaziar a fila com poll até que esteja vazia.
Um exemplo prático: produtor/consumidor em uma thread
O programa abaixo usa um ArrayDeque como fila FIFO para modelar um pequeno sistema de impressão: trabalhos chegam ao longo do tempo (representamos isso usando offer em lotes), e o trabalhador esvazia cada lote com poll. O par de estilos de erro é mostrado no topo para que você veja exatamente quando cada forma é acionada.
Alguns pontos a observar na execução:
pollepeekretornaram silenciosamentenullquando a fila estava vazia;removeeelementlançaram exceções. Ambos os comportamentos fazem parte do contrato — escolha o que combina com o fato de "vazio" ser um erro ou apenas um estado.offer(null)lançou exceção emArrayDeque. É a implementação impondo a regra da qual a interface depende.- O
PriorityQueueremoveu10, 20, 30, 40— ordem classificada, não ordem de inserção. Mesma interfaceQueue, regra de seleção de cabeça completamente diferente.
O que vem a seguir
A primeira implementação não-FIFO merece seu próprio capítulo — PriorityQueue é uma fila respaldada por heap onde a cabeça é sempre o menor elemento. Esse é o ingrediente básico de praticamente todo agendador de "processar a tarefa mais urgente a seguir" no JDK.