Queue y Deque: El Arte de Organizar Datos (Sin Hilos)
Introducción: ¿Fila india o Baraja de cartas?
¡Hola de nuevo, equipo de desarrollo! 👨💻👩💻
Ya dominamos la LinkedList como estructura, pero hoy vamos a centrarnos en el comportamiento. Imaginad que estáis programando la lógica de una caja de supermercado. No puedes atender al último cliente primero (se armaría un escándalo). Necesitas un orden estricto: el primero que llega, es el primero que sale. Eso es una Queue (Cola).
Ahora imaginad que estáis programando el botón "Deshacer" de un editor de texto o jugando a las cartas. Lo último que hiciste es lo primero que quieres borrar. O quizá quieres hacer trampas y sacar una carta de abajo del mazo. Necesitas flexibilidad por ambos extremos. Eso es una Deque (Double Ended Queue).
Hoy vamos a ver estas interfaces puras, sin meternos en líos de concurrencia ni multihilo. Solo tú, tus datos y el orden perfecto.
Conceptos Fundamentales
Definición: Queue (Cola)
Es una interfaz que define una colección lineal ordenada. Ordena los elementos en formato FIFO (Primero en entrar, primero en salir). * Head (Cabeza): El elemento que lleva más tiempo esperando. El siguiente en salir. * Tail (Cola): Donde se añaden los nuevos elementos. * Uso típico: Cola de impresión, lista de reproducción de canciones, gestión de pedidos.
Definición: Implementaciones Comunes
Como Queue es un contrato (interface), solemos instanciarla usando:
* LinkedList: La opción estándar para colas de propósito general.
* PriorityQueue: Una cola "tramposa" donde salen primero los más importantes (según un criterio de orden), no los que llegaron antes.
* ArrayDeque: Más rápida y moderna que LinkedList, pero no admite nulos.
Definición: Deque (Double Ended Queue)
Es una interfaz que extiende de Queue pero permite insertar, eliminar y consultar elementos por ambos extremos (cabeza y cola).
* Uso típico: Pilas (Stacks - LIFO), historial de navegación, palíndromos.
Implementaciones Clave
Las dos clases principales que usaréis son:
1. LinkedList: Implementa tanto Queue como Deque.
2. ArrayDeque: Implementa Deque. Es más rápida y eficiente en memoria que LinkedList, pero no permite nulos.
Mapa Mental: Estructura de Interfaces
Desarrollo y Ejemplos Prácticos
Vamos a desglosar TODOS los métodos solicitados. Para que sea fácil de estudiar, los dividiremos en tres categorías: Insertar, Eliminar y Consultar, diferenciando siempre entre los métodos "Dramáticos" (Lanzan Excepción) y los "Tranquilos" (Devuelven valores especiales).
1. Métodos de Queue (Cola Simple)
Usaremos una LinkedList actuando como Queue para simular una Cola de Impresión.
1. Inserción: ¡Al final de la fila!
Aquí añadimos elementos a la cola (Tail).
add(E e): Intenta añadir. Si no puede (ej. cola con límite de capacidad), lanza una excepciónIllegalStateException.offer(E e): Intenta añadir. Si no puede, simplemente devuelvefalse. Es el método recomendado para colas con capacidad restringida.
import java.util.LinkedList;
import java.util.Queue;
public class GestionCola {
public static void main(String[] args) {
// Instanciamos una LinkedList pero la tratamos como Queue.
// Polimorfismo: Solo vemos los métodos de la interfaz Queue.
Queue<String> colaSupermercado = new LinkedList<>();
// --- MÉTODOS DE INSERCIÓN ---
// 1. add(E e) - El método "Exigente"
// Añade "Cliente A" al final.
// Si la cola tuviera un límite fijo y estuviera llena, lanzaría Exception.
colaSupermercado.add("Cliente A (La señora de las monedas)");
System.out.println("Añadido con add: Cliente A");
// 2. offer(E e) - El método "Educado"
// Intenta añadir "Cliente B". Devuelve true si lo logra.
// Si la cola estuviera llena, devolvería false (sin errores rojos).
boolean aceptado = colaSupermercado.offer("Cliente B (El chico de la prisa)");
if (aceptado) {
System.out.println("Añadido con offer: Cliente B");
} else {
System.out.println("Cola llena, vuelva más tarde.");
}
System.out.println("Estado actual: " + colaSupermercado);
// Salida: [Cliente A..., Cliente B...]
}
}
2. Eliminación: ¡El siguiente!
Aquí sacamos al elemento que está en la cabeza (Head) para procesarlo.
remove(): Saca y devuelve la cabeza. Si la cola está vacía, ¡CRASH! (NoSuchElementException).poll(): Saca y devuelve la cabeza. Si la cola está vacía, devuelvenull.
// ... (continuación del código anterior)
// --- MÉTODOS DE ELIMINACIÓN ---
// 3. remove() - El método "Arriesgado"
// Atendemos al primero (Cliente A).
// ¡CUIDADO! Si ejecutamos esto en una cola vacía, el programa se detiene con error.
String atendido1 = colaSupermercado.remove();
System.out.println("Atendiendo a (remove): " + atendido1);
// 4. poll() - El método "Seguro"
// Atendemos al siguiente (Cliente B).
// Es ideal para bucles while, porque cuando se vacía, devuelve null y paramos.
String atendido2 = colaSupermercado.poll();
System.out.println("Atendiendo a (poll): " + atendido2);
// Intento sacar a alguien más, pero la cola está vacía...
String fantasma = colaSupermercado.poll();
System.out.println("¿Queda alguien? (poll): " + fantasma); // Imprime: null
// Si descomentamos la siguiente línea, el programa fallaría:
// colaSupermercado.remove(); // Lanza NoSuchElementException
3. Inspección: ¿Quién va ahora?
A veces solo queremos ver quién es el siguiente sin sacarlo de la fila (quizás para preparar su pedido).
element(): Recupera la cabeza pero NO la elimina. Si está vacía, lanza Excepción.peek(): Recupera la cabeza pero NO la elimina. Si está vacía, devuelvenull.
import java.util.LinkedList;
import java.util.Queue;
public class MirandoLaCola {
public static void main(String[] args) {
Queue<String> impresora = new LinkedList<>();
impresora.offer("Tesis_Final.pdf");
impresora.offer("Meme_Gato.jpg");
// --- MÉTODOS DE INSPECCIÓN ---
// 5. element() - Mirada "Agresiva"
// Mira quién es el primero. Si no hay nadie, lanza excepción.
// Útil cuando tu lógica asegura que SIEMPRE debe haber algo.
System.out.println("Siguiente documento (element): " + impresora.element());
// 6. peek() - Mirada "Discreta"
// Mira quién es el primero. Si está vacía, devuelve null.
// Es la forma más segura de comprobar antes de procesar.
System.out.println("Siguiente documento (peek): " + impresora.peek());
// Demostración de que NO se han borrado:
System.out.println("Tamaño sigue siendo: " + impresora.size()); // 2
}
}
4. Operaciones Generales de Colección
Como Queue hereda de Collection, tenemos herramientas estándar para gestión masiva.
size(): Cuenta elementos.isEmpty(): Verifica si está vacía.contains(Object o): Busca un elemento (usaequals()).iterator(): Nos da un objeto para recorrer la cola uno a uno.toArray(): Convierte la cola en un Array clásico.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Iterator;
import java.util.Arrays;
public class UtilidadesCola {
public static void main(String[] args) {
Queue<Integer> numeros = new LinkedList<>();
numeros.add(10);
numeros.add(20);
numeros.add(30);
// 7. size() - Tamaño
System.out.println("Hay " + numeros.size() + " números en cola.");
// 8. isEmpty() - Verificación
if (!numeros.isEmpty()) {
System.out.println("La cola tiene datos.");
}
// 9. contains(Object o) - Búsqueda
// Ojo: Esto puede ser lento (O(n)) en LinkedList porque recorre todo.
boolean tieneVeinte = numeros.contains(20);
System.out.println("¿Está el 20 esperando? " + tieneVeinte);
// 10. iterator() - Recorrido manual
System.out.print("Iterando la cola: ");
Iterator<Integer> it = numeros.iterator();
while(it.hasNext()) {
// next() nos da el elemento y avanza el cursor
System.out.print("[" + it.next() + "] ");
}
System.out.println();
// 11. toArray() - Conversión
// A veces necesitamos pasar los datos a una API antigua que pide arrays.
Object[] array = numeros.toArray();
System.out.println("Convertido a Array: " + Arrays.toString(array));
// Limpiamos todo
numeros.clear();
System.out.println("Después de clear(), tamaño: " + numeros.size());
}
}
Inline Tip: Iteración
Aunque podemos usar un bucle for-each (for (Integer n : numeros)), internamente Java usa el método iterator() que acabamos de ver. Es azúcar sintáctico. 🍬
Diagrama de Flujo: ¿Qué método elijo?
Es fácil liarse entre poll, remove, peek... Este diagrama es vuestra brújula.
graph TD
Start((Inicio)) --> Action["¿Qué quieres hacer?"]
Action -- Meter Dato --> Insert[Inserción]
Action -- Sacar Dato --> Remove[Eliminación]
Action -- Solo Mirar --> Inspect[Inspección]
Insert --> I_Q["¿Es crítico si falla?"]
I_Q -- "Sí, quiero Excepción" --> I_Ex["add"]
I_Q -- "No, avísame con false" --> I_Safe["offer"]
Remove --> R_Q["¿La cola podría estar vacía?"]
R_Q -- "No, seguro hay datos" --> R_Ex["remove"]
R_Q -- "Quizás, quiero evitar error" --> R_Safe["poll"]
Inspect --> In_Q["¿La cola podría estar vacía?"]
In_Q -- "No, seguro hay datos" --> In_Ex["element"]
In_Q -- "Quizás, devuelve null" --> In_Safe["peek"]
2. Métodos de Deque (Doble Extremo)
Definición: Deque
Viene de "Double Ended Queue" (Cola de doble extremo). Es una interfaz que extiende de Queue. A diferencia de una cola normal (que es solo FIFO), una Deque permite insertar, inspeccionar y eliminar elementos tanto por el principio (Head) como por el final (Tail).
Definición: ArrayDeque
Es la implementación de Deque basada en un array redimensionable (como ArrayList, pero circular internamente).
* Ventaja clave: Es más rápida que Stack y que LinkedList para operaciones de cola/pila.
* Restricción importante: NO admite elementos null.
Mapa Mental: El Ecosistema Deque
Aquí usamos ArrayDeque (más eficiente). Imaginad una Baraja de Cartas.
A. Inserción en Extremos (Insert First/Last)
| Dramático (Excepción) | Seguro (Return false/null) | Ubicación |
|---|---|---|
addFirst(e) |
offerFirst(e) |
Principio (Arriba) |
addLast(e) |
offerLast(e) |
Final (Abajo) |
import java.util.ArrayDeque;
import java.util.Deque;
public class EjemploInsertionDeque {
public static void main(String[] args) {
Deque<String> baraja = new ArrayDeque<>();
// Inserción al PRINCIPIO (Arriba del mazo)
baraja.addFirst("As de Corazones"); // void / Excepción si falla
boolean ok1 = baraja.offerFirst("Rey de Picas"); // boolean
// Inserción al FINAL (Abajo del mazo)
baraja.addLast("2 de Tréboles"); // void / Excepción si falla
boolean ok2 = baraja.offerLast("Joker"); // boolean
System.out.println(baraja);
// Orden: [Rey de Picas, As de Corazones, 2 de Tréboles, Joker]
}
}
B. Eliminación en Extremos (Remove First/Last)
| Dramático (Excepción) | Seguro (Return null) | Ubicación |
|---|---|---|
removeFirst() |
pollFirst() |
Principio |
removeLast() |
pollLast() |
Final |
import java.util.ArrayDeque;
import java.util.Deque;
public class EjemploRemovalDeque {
public static void main(String[] args) {
Deque<String> baraja = new ArrayDeque<>();
baraja.add("Carta 1");
baraja.add("Carta 2");
// Sacar de ARRIBA (First)
String c1 = baraja.removeFirst(); // Lanza Excepción si vacía
String c2 = baraja.pollFirst(); // Devuelve null si vacía
// Sacar de ABAJO (Last) - Intentamos sacar de lista vacía
// String error = baraja.removeLast(); // ¡CRASH! NoSuchElementException
String seguro = baraja.pollLast(); // null, todo bien.
System.out.println("Seguro: " + seguro);
}
}
C. Inspección en Extremos (Get/Peek First/Last)
| Dramático (Excepción) | Seguro (Return null) | Ubicación |
|---|---|---|
getFirst() |
peekFirst() |
Principio |
getLast() |
peekLast() |
Final |
import java.util.ArrayDeque;
import java.util.Deque;
public class EjemploInspectionDeque {
public static void main(String[] args) {
Deque<String> historial = new ArrayDeque<>();
historial.add("Google.com");
historial.add("Youtube.com");
// Mirar el primero (First - Google)
System.out.println("First (Dramático): " + historial.getFirst());
System.out.println("First (Seguro): " + historial.peekFirst());
// Mirar el último (Last - Youtube)
System.out.println("Last (Dramático): " + historial.getLast());
System.out.println("Last (Seguro): " + historial.peekLast());
// No modifican la lista, solo miran.
System.out.println("Tamaño sigue siendo: " + historial.size());
}
}
1. Inserción (Insertion): Llenando la baraja
Aquí metemos datos. Podemos hacerlo como una cola normal o especificando el extremo.
Métodos:
* add(E e) / addLast(E e): Añade al final. Excepción si falla.
* offer(E e) / offerLast(E e): Añade al final. false si falla.
* addFirst(E e): Añade al principio. Excepción si falla.
* offerFirst(E e): Añade al principio. false si falla.
Ejemplo 1: Gestión de Tareas (Priority Tasks)
Imaginad un sistema donde entran tareas normales (al final) y tareas urgentes (al principio).
import java.util.ArrayDeque;
import java.util.Deque;
public class TaskManager {
public static void main(String[] args) {
// Creamos un ArrayDeque. No necesitamos definir tamaño inicial.
Deque<String> tareas = new ArrayDeque<>();
// --- Inserción Estándar (Al final) ---
// add(E e): Lanza excepción si no hay espacio (raro en ArrayDeque)
tareas.add("Revisar correos");
// offer(E e): Devuelve true/false. Más seguro.
boolean aceptada = tareas.offer("Actualizar Java");
// addLast y offerLast hacen EXACTAMENTE lo mismo que add y offer
tareas.addLast("Limpiar escritorio");
tareas.offerLast("Ir a por café");
// --- Inserción Prioritaria (Al principio) ---
// ¡Jefe entra en la oficina! Tarea urgente al principio (Head)
tareas.addFirst("URGENTE: Servidor caído");
// offerFirst devuelve false si fallara (ej. deque con capacidad limitada)
tareas.offerFirst("SUPER URGENTE: Fuego en la sala de servidores");
System.out.println("Lista de tareas: " + tareas);
// Salida esperada:
// [SUPER URGENTE..., URGENTE..., Revisar..., Actualizar..., Limpiar..., Ir a por café]
}
}
2. Eliminación (Removal): Repartiendo cartas
Sacamos elementos. Si es una Cola (FIFO), sacamos del principio. Si es Pila (LIFO), también del principio (pero habríamos insertado al principio).
Métodos:
* remove() / removeFirst(): Saca del principio. Excepción si vacía.
* poll() / pollFirst(): Saca del principio. null si vacía.
* removeLast(): Saca del final. Excepción si vacía.
* pollLast(): Saca del final. null si vacía.
Ejemplo 2: El Historial de Navegación (Undo/Redo)
Vamos a simular un botón de "Deshacer" (sacar lo último que entró).
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
public class BrowserHistory {
public static void main(String[] args) {
Deque<String> historial = new ArrayDeque<>();
// Llenamos el historial
historial.push("google.com"); // push() es alias de addFirst()
historial.push("youtube.com");
historial.push("stackoverflow.com");
// Estructura actual: [stackoverflow, youtube, google] -> Principio a la izq
// --- Eliminación Dramática (Exceptions) ---
try {
// remove() y removeFirst() son lo mismo: sacan la cabeza
String paginaActual = historial.removeFirst();
System.out.println("Saliendo de: " + paginaActual); // stackoverflow.com
} catch (NoSuchElementException e) {
System.out.println("Historial vacío.");
}
// --- Eliminación Prudente (Null safe) ---
// poll() y pollFirst() son lo mismo.
String anterior = historial.pollFirst();
System.out.println("Volviendo a: " + anterior); // youtube.com
// --- Operación desde el final (Tail) ---
// Imaginad que el historial tiene un límite y borramos lo más antiguo
String masVieja = historial.pollLast(); // Saca del final
System.out.println("Olvidando la página más vieja: " + masVieja); // google.com
// Ahora está vacía. Veamos qué pasa.
System.out.println("¿Queda algo? " + historial.poll()); // null
// System.out.println(historial.remove()); // ¡ESTO EXPLOTA!
}
}
3. Inspección (Inspection): Solo mirando
Queremos ver qué hay sin tocar nada.
Métodos:
* element() / getFirst(): Mira el principio. Excepción si vacía.
* peek() / peekFirst(): Mira el principio. null si vacía.
* getLast(): Mira el final. Excepción si vacía.
* peekLast(): Mira el final. null si vacía.
Ejemplo 3: El Portero de Discoteca
Comprobamos quién es el primero y el último de la fila.
import java.util.ArrayDeque;
import java.util.Deque;
public class ClubQueue {
public static void main(String[] args) {
Deque<String> filaVip = new ArrayDeque<>();
filaVip.add("Celebridad A");
filaVip.add("Celebridad B");
filaVip.add("Yo (Colado)");
// --- Mirar al Principio ---
// peek() y peekFirst() devuelven null si no hay nadie, no arriesgan.
System.out.println("Siguiente en entrar: " + filaVip.peek());
// element() y getFirst() lanzan error si está vacía.
System.out.println("Siguiente (Confirmado): " + filaVip.getFirst());
// --- Mirar al Final ---
// ¿Quién es el último pringado de la fila?
System.out.println("Último de la fila: " + filaVip.peekLast());
// Limpiamos la sala
filaVip.clear();
// Prueba de fuego
if (filaVip.peekFirst() == null) {
System.out.println("La fila está vacía (Comprobado con peek).");
}
// filaVip.getLast(); // ¡ESTO LANZARÍA EXCEPCIÓN NoSuchElementException!
}
}
4. Gestión General y Conversión
Métodos comunes de la interfaz Collection.
Métodos:
* size(): Entero con el número de elementos.
* isEmpty(): Booleano (true si size es 0).
* contains(Object o): Booleano. Busca con .equals().
* iterator(): Devuelve un iterador estándar.
* toArray(): Devuelve un Object[] con los datos.
Ejemplo 4: Añadiendo números
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Arrays;
public class CollectionMethods {
public static void main(String[] args) {
Deque<Integer> numeros = new ArrayDeque<>();
numeros.add(10);
numeros.add(20);
numeros.add(30);
// 1. Información Básica
System.out.println("Tamaño: " + numeros.size()); // 3
System.out.println("¿Está vacía? " + numeros.isEmpty()); // false
System.out.println("¿Contiene el 20? " + numeros.contains(20)); // true
// 2. Convertir a Array
Object[] arraySimple = numeros.toArray();
System.out.println("Array: " + Arrays.toString(arraySimple));
// 3. Iterador
System.out.print("Recorriendo: ");
Iterator<Integer> it = numeros.iterator();
while (it.hasNext()) {
Integer n = it.next();
System.out.print(n + " - ");
}
// Salida: 10 - 20 - 30 -
}
}
Diagrama Mermaid: Resumen Visual de Métodos
Para no liarnos con tantos nombres parecidos, este diagrama os salvará la vida en el examen.
graph TD
subgraph "Queue & Deque Operations"
direction LR
A[Operación] --> B{¿Qué pasa si falla/está vacía?}
B -- "Lanza Excepción 💥" --> C[Grupo ADD / REMOVE / GET-ELEMENT]
B -- "Devuelve null/false 🛡️" --> D[Grupo OFFER / POLL / PEEK]
C --> C1[add / addFirst / addLast]
C --> C2[remove / removeFirst / removeLast]
C --> C3[element / getFirst / getLast]
D --> D1[offer / offerFirst / offerLast]
D --> D2[poll / pollFirst / pollLast]
D --> D3[peek / peekFirst / peekLast]
end
Tabla Comparativa: ¿Cuál usar?
No se trata solo de gustos, sino de robustez en el código.
| Escenario | Método recomendado | Razón |
|---|---|---|
| Inicialización garantizada | add, remove, getFirst |
Si sabes seguro que hay datos, la excepción te avisa de un bug lógico. |
| Flujo incierto | offer, poll, peek |
Si es normal que la cola esté llena o vacía y quieres controlarlo con un if. |
| Pilas (Stacks) | push (equivale a addFirst), pop (removeFirst) |
Nombres más semánticos para estructuras LIFO. |
| Colas (Queues) | offer, poll |
Estándar de la industria para procesamiento FIFO. |
Consejo Pro: LinkedList vs ArrayDeque
- Usa
ArrayDequepor defecto. Es más rápida y consume menos memoria porque usa un array redimensionable internamente en vez de crear nodos dispersos. - Usa
LinkedListSOLO si necesitas implementar unaLista la vez (acceso por índiceget(i)) o si necesitas insertar elementosnull(ArrayDeque prohíbe los nulos).
Aplicación en el Mundo Real
-
Algoritmo de Búsqueda en Anchura (BFS): Cuando un GPS busca una ruta o un algoritmo busca amigos en común en una red social, utiliza una Queue.
- Empieza por TI. Añade tus amigos a la cola.
- Saca al primero (
poll), comprueba si es el destino. - Si no, añade los amigos de ese amigo a la cola (
offer). - Al ser FIFO, explora por "círculos" o niveles de cercanía.
-
Palíndromos y Análisis de Texto: Para comprobar si una palabra es un palíndromo ("DABALE ARROZ A LA ZORRA EL ABAD"), puedes cargar los caracteres en un Deque.
- Comparas
pollFirst()(primera letra) conpollLast()(última letra). - Si son iguales, sigues. Si llegas al centro, es palíndromo.
- Comparas
-
Gestor de Impresión (Spooler): Es el ejemplo clásico. Varios usuarios envían documentos. Se almacenan en una Queue. El servicio de impresión hace un bucle infinito
while(!queue.isEmpty())haciendopoll()para imprimir uno a uno en orden de llegada.
Para Saber Más
-
Java Docs - Queue Interface: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Queue.html
-
Java Docs - Deque Interface: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Deque.html
-
Understanding ArrayDeque (The hidden gem): Un artículo técnico excelente sobre por qué
ArrayDequesuele ser mejor queLinkedList. https://www.baeldung.com/java-array-deque