Interfaz Queue en Java
Operaciones de cola FIFO en Java con la interfaz Queue: offer, poll, peek y sus implementaciones.
Queue<E> es Collection<E> más el concepto de una cabeza. La cabeza es el siguiente elemento que se eliminará; todos los demás esperan su turno detrás de ella. La semántica predeterminada — y la que casi todas las implementaciones del JDK utilizan — es FIFO: primero en entrar, primero en salir. Los productores offer elementos al final; los consumidores los obtienen con poll desde la cabeza. Este capítulo trata sobre el contrato: los seis métodos, los dos estilos de manejo de errores, la regla sobre valores null, y qué implementación usar en cada situación.
Dos formas de hacer cada cosa — por diseño
Cada operación de cola tiene dos variantes: una que lanza una excepción en caso de fallo, y otra que devuelve un valor especial. El Javadoc lo presenta como una cuadrícula de 3×2:
| Operación | Lanza en caso de fallo | Devuelve valor especial |
|---|---|---|
| Insertar | boolean add(E e) (lanza IllegalStateException si está llena) | boolean offer(E e) (devuelve false si está llena) |
| Eliminar | E remove() (lanza NoSuchElementException si está vacía) | E poll() (devuelve null si está vacía) |
| Examinar | E element() (lanza NoSuchElementException si está vacía) | E peek() (devuelve null si está vacía) |
La columna "lanza" es la heredada de Collection y List — es familiar pero poco práctica cuando el caso vacío/lleno es normal (consumidor esperando el siguiente trabajo, cola acotada rechazando un productor en exceso). La columna "devuelve valor especial" se añadió para las colas para que puedas escribir bucles que no dependan de excepciones para el flujo de control:
String item;
while ((item = queue.poll()) != null) {
process(item);
}Usa la forma que devuelve por defecto. Recurre a remove/element solo cuando una cola vacía en ese punto sería un error genuino que querrías que se manifestara como una excepción.
Null y Queue no se mezclan
Dado que poll() y peek() devuelven null para indicar "vacío", permitir null como un elemento real es una receta para la ambigüedad. Todas las Queue de propósito general en el JDK excepto LinkedList rechazan null — ArrayDeque, PriorityQueue, ArrayBlockingQueue, todas las colas concurrentes. LinkedList te permite añadir un null, pero si lo haces has roto el contrato: ya no hay forma de distinguir "la cabeza es null" de "la cola está vacía."
La regla: no almacenes null en una cola. Elige ArrayDeque sobre LinkedList y el lenguaje lo hace cumplir por ti.
FIFO es el comportamiento por defecto — pero no universal
Queue no requiere FIFO. La interfaz define una cabeza y operaciones que extraen de ella; cómo se determina la cabeza depende de la implementación. Las dos implementaciones en java.util que no son FIFO son:
PriorityQueue— la cabeza es siempre el elemento más pequeño (según el orden natural o unComparator). Las inserciones van donde el heap lo indica, no al final.- Las variantes bloqueantes en
java.util.concurrent—PriorityBlockingQueue,DelayQueue, etc. — reordenan por prioridad, plazo, etc.
Todo lo demás (ArrayDeque, LinkedList, ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue) es FIFO.
Elegir una implementación
Para el caso de un solo hilo:
ArrayDeque— la opción predeterminada. Array circular, sin asignación por elemento, rápido. Úsalo comoQueue<E>o comoDeque<E>dependiendo de si necesitas acceso en ambos extremos.LinkedList— funciona, también implementaList. Elige solo si genuinamente necesitas ambas interfaces en el mismo objeto. El capítulo sobre LinkedList cubre las ventajas y desventajas.PriorityQueue— cuando quieres "el elemento más pequeño en espera a continuación" en lugar de FIFO.
Para el caso de múltiples hilos (cubierto en detalle en la parte de concurrencia más adelante — solo referencias aquí):
ConcurrentLinkedQueue— FIFO sin bloqueos. Sin límite.ArrayBlockingQueue— acotada, respaldada por un array, bloqueaputcuando está llena ytakecuando está vacía. La cola productor/consumidor clásica.LinkedBlockingQueue— opcionalmente acotada, versión con nodos enlazados de la misma.PriorityBlockingQueue—PriorityQueueconcurrente. Sin límite.
La mayoría del código de aplicación usa una de las primeras cuatro. Las colas bloqueantes son la forma de construir grupos de workers y back-pressure.
Qué puedes llamar en cualquier Queue
Además de los seis métodos específicos de la cabeza, una Queue también es una Collection — por lo que size, isEmpty, contains, iterator, forEach, stream, clear, toArray funcionan. El orden de iteración es el orden en que se extraerían los elementos para las implementaciones FIFO (ArrayDeque, LinkedList), pero para PriorityQueue el orden del iterador no es el orden de prioridad — visita el array del heap subyacente tal como está. Si quieres los elementos en orden de prioridad, tienes que vaciar la cola con poll hasta que esté vacía.
Un ejemplo práctico: productor/consumidor en un solo hilo
El programa a continuación usa un ArrayDeque como cola FIFO para modelar un pequeño sistema de trabajos de impresión: los trabajos llegan con el tiempo (lo representamos haciendo offer en lotes), y el worker vacía cada lote con poll. El par de estilos de error se muestra al principio para que puedas ver exactamente cuándo se activa cada forma.
Algunos puntos a destacar de la ejecución:
pollypeekdevolvieron silenciosamentenullcuando la cola estaba vacía;removeyelementlanzaron excepciones. Ambos comportamientos son parte del contrato — elige el que corresponda según si "vacío" es un error o simplemente un estado.offer(null)lanzó una excepción enArrayDeque. Eso es la implementación haciendo cumplir la regla de la que depende la interfaz.- La
PriorityQueueextrajo10, 20, 30, 40— en orden ordenado, no en orden de inserción. La misma interfazQueue, una regla de selección de cabeza completamente diferente.
Qué sigue
La primera implementación no FIFO merece su propio capítulo — PriorityQueue es una cola respaldada por un heap donde la cabeza es siempre el elemento más pequeño. Ese es el ingrediente básico de prácticamente todos los planificadores de "procesar la tarea más urgente a continuación" en el JDK.