W3docs

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ónLanza en caso de falloDevuelve valor especial
Insertarboolean add(E e) (lanza IllegalStateException si está llena)boolean offer(E e) (devuelve false si está llena)
EliminarE remove() (lanza NoSuchElementException si está vacía)E poll() (devuelve null si está vacía)
ExaminarE 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 nullArrayDeque, 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 un Comparator). Las inserciones van donde el heap lo indica, no al final.
  • Las variantes bloqueantes en java.util.concurrentPriorityBlockingQueue, 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 como Queue<E> o como Deque<E> dependiendo de si necesitas acceso en ambos extremos.
  • LinkedList — funciona, también implementa List. 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, bloquea put cuando está llena y take cuando está vacía. La cola productor/consumidor clásica.
  • LinkedBlockingQueue — opcionalmente acotada, versión con nodos enlazados de la misma.
  • PriorityBlockingQueuePriorityQueue concurrente. 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.

java— editable, runs on the server

Algunos puntos a destacar de la ejecución:

  • poll y peek devolvieron silenciosamente null cuando la cola estaba vacía; remove y element lanzaron 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 en ArrayDeque. Eso es la implementación haciendo cumplir la regla de la que depende la interfaz.
  • La PriorityQueue extrajo 10, 20, 30, 40 — en orden ordenado, no en orden de inserción. La misma interfaz Queue, 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.

Práctica

Práctica
Dentro de un bucle escribes `while ((job = queue.poll()) != null) { process(job); }`. Si en cambio usaras `queue.remove()`, ¿qué cambiaría a nivel de contrato?
Dentro de un bucle escribes `while ((job = queue.poll()) != null) { process(job); }`. Si en cambio usaras `queue.remove()`, ¿qué cambiaría a nivel de contrato?
Was this page helpful?