Interfaz Deque de Java
Operaciones de cola de doble extremo en Java con la interfaz Deque: addFirst, addLast, pollFirst, pollLast.
Deque<E> — pronunciado "deck," abreviación de double-ended queue (cola de doble extremo) — es Queue<E> con un segundo extremo. Donde una Queue solo permite eliminar desde la cabeza, una Deque permite insertar, eliminar y examinar en ambos extremos: la cabeza y la cola. Esa única capacidad adicional es suficiente para hacer de Deque el contrato detrás de dos abstracciones completamente diferentes: es una cola, es una pila, y el JDK la recomienda como la opción predeterminada para ambas.
Dos extremos × tres operaciones × dos estilos de error
La interfaz parece intimidante al principio porque cada operación aparece en doce formas: cabeza vs cola, insertar vs eliminar vs examinar, lanza excepción vs devuelve valor especial. Pero es simplemente la cuadrícula 3×2 de Queue extendida a dos extremos:
| Operación | Primero (cabeza) — lanza | Primero (cabeza) — valor especial | Último (cola) — lanza | Último (cola) — valor especial |
|---|---|---|---|---|
| Insertar | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
| Eliminar | removeFirst() | pollFirst() | removeLast() | pollLast() |
| Examinar | getFirst() | peekFirst() | getLast() | peekLast() |
La columna "lanza" es la que se llama cuando una deque vacía/llena en ese punto sería un error. La columna "valor especial" es la que se llama cuando el estado vacío es esperado y se desea comprobar en una condición de bucle.
El mapeo de cola
Deque extiende Queue, por lo que una Deque es una Queue. Los métodos heredados son alias de las variantes de cabeza a cola:
Método de Queue | Equivalente en Deque |
|---|---|
add(e) / offer(e) | addLast(e) / offerLast(e) |
remove() / poll() | removeFirst() / pollFirst() |
element() / peek() | getFirst() / peekFirst() |
Insertar al final, eliminar desde la cabeza: esa es la disciplina FIFO que una Queue ya proporciona, simplemente escrita con los nombres explícitos de cada extremo.
El mapeo de pila
Deque también define tres métodos "pila" que actúan sobre la cabeza:
| Método de pila | Equivalente en Deque |
|---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
Apilar y desapilar: disciplina LIFO, misma cabeza, extremo opuesto al insertar/eliminar del mapeo de cola. Por eso el capítulo de Stack te dirige aquí: la forma moderna de escribir una pila en Java es Deque<E> stack = new ArrayDeque<>(); y llamar a push / pop / peek.
La regla: null no está permitido
Al igual que Queue, el contrato de Deque reserva null como el centinela de "vacío" para pollFirst, pollLast, peekFirst, peekLast. Por ello, cada Deque de propósito general en el JDK rechaza los elementos null con NullPointerException al insertar. La única excepción es LinkedList, que permite null por razones históricas — y hereda toda la ambigüedad que eso conlleva. No hagas eso.
Iteración e iteración inversa
Una Deque tiene dos iteradores por convención:
iterator()recorre de cabeza a cola — el orden en que se llamaríapollFirstrepetidamente.descendingIterator()recorre de cola a cabeza — el orden en que se llamaríapollLastrepetidamente.
Ambos son típicamente fail-fast: mutar la deque desde fuera del iterador mientras se itera lanza ConcurrentModificationException. Usa Iterator.remove() o removeIf si necesitas mutar durante un recorrido.
Elegir una implementación
En código monohilo hay esencialmente dos opciones:
ArrayDeque— la opción predeterminada. Array circular, O(1) en ambos extremos, sin asignación de nodos por elemento, amigable con la caché. Rechazanull. ImplementaDequeyQueue.LinkedList— nodos doblemente enlazados. También implementaList, por lo que cada elemento obtiene un nodo con punterosprev/next. Más lenta en ambos extremos queArrayDequey usa más memoria. Elígela solo si realmente necesitas semántica deDequeyListen el mismo objeto.
Para código concurrente (tratado más adelante en la parte de concurrencia del libro):
ConcurrentLinkedDeque— sin bloqueos, sin límite.LinkedBlockingDeque— opcionalmente acotada, bloqueante — la versión que se usaría para construir una cola de robo de trabajo o un productor/consumidor con contrapresión en cualquier extremo.
Un ejemplo completo: cola, pila y verificación de palíndromo en una sola deque
El programa siguiente utiliza un ArrayDeque como cola FIFO, otro como pila LIFO y un tercero para comprobar si una frase es un palíndromo alimentando ambos extremos y comparando. La idea es que la misma interfaz, e incluso la misma clase, desempeña los tres roles.
Lo que muestra esta ejecución:
- La misma clase cumple los roles de cola y pila sin renombrar ningún método más allá de qué extremo se direcciona.
- Los métodos de "valor de retorno especial" producen silenciosamente
nullcuando están vacíos; los métodos de "lanza" generanNoSuchElementException. Elige según si el estado vacío es esperado o un error. - La comprobación de palíndromo utiliza ambos extremos en el mismo bucle —
pollFirstypollLastjuntos. Ese es el patrón de acceso que solo unaDequeofrece de forma eficiente.
Qué sigue
El capítulo de Deque cierra la parte del framework orientada a colas. La otra gran abstracción en Collection es la que rechaza duplicados: un Set es una Collection con el contrato de unicidad incorporado. Lo veremos a continuación.