The Queue
interface in Java represents a collection designed for holding elements prior to processing. It follows the First-In-First-Out (FIFO) principle, where elements are added to the end of the queue and removed from the front. Queues are commonly used in task scheduling, buffering, and event-driven programming.
The Queue
interface provides the following primary methods:
offer(E e)
– Inserts the specified element into the queue. Returns true
if successful, or false
if the queue is full (in bounded queues).poll()
– Retrieves and removes the head of the queue, or returns null
if the queue is empty.peek()
– Retrieves, but does not remove, the head of the queue, or returns null
if the queue is empty.These methods are non-exception throwing alternatives to add()
, remove()
, and element()
from the Collection
interface.
Java provides several classes that implement the Queue
interface. Among the most commonly used is:
LinkedList
List
and Queue
interfaces.Example:
import java.util.*;
Queue<String> queue = new LinkedList<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println(queue.poll()); // A
System.out.println(queue.peek()); // B
System.out.println(queue); // [B, C]
In this example, "A"
is removed first, demonstrating FIFO behavior.
While Queue
is primarily single-ended (add to rear, remove from front), the Deque
interface (double-ended queue) extends Queue
and allows insertion and removal from both ends. This makes Deque
useful for implementing both queues and stacks (LIFO).
Feature | Queue | Deque |
---|---|---|
Add Element | offer() | offerFirst(), offerLast() |
Remove Element | poll() | pollFirst(), pollLast() |
Peek Element | peek() | peekFirst(), peekLast() |
Common Deque
implementations include ArrayDeque
and LinkedList
.
Queue
interface supports FIFO-style processing with methods like offer()
, poll()
, and peek()
.LinkedList
is a versatile implementation that works well for most general-purpose queue needs.Deque
instead.In the following sections, we’ll explore specialized queues like PriorityQueue
and see practical examples of using different types of queues and deques in real-world scenarios.
The PriorityQueue
in Java is a special kind of queue where elements are ordered based on priority, rather than their insertion order. Unlike standard Queue
implementations like LinkedList
, which operate in a First-In-First-Out (FIFO) manner, a PriorityQueue
retrieves elements according to natural ordering or a custom comparator.
Internally, PriorityQueue
is implemented using a binary heap — a complete binary tree where each parent node is smaller than its child nodes (for a min-heap). The default PriorityQueue
in Java is a min-heap, meaning the smallest element (based on natural ordering or a comparator) is always at the front of the queue.
Operations like insertion (offer
) and removal (poll
) maintain the heap invariant and have logarithmic time complexity (O(log n)
).
If elements are added without a custom comparator, PriorityQueue
uses their natural ordering, defined by the Comparable
interface. For instance, Integer
sorts in ascending order.
import java.util.PriorityQueue;
public class NaturalPriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(42);
pq.offer(15);
pq.offer(30);
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // Prints: 15, 30, 42
}
}
}
Explanation: Although 42 was added first, 15 (the smallest) is retrieved first due to natural ordering.
For more control over element priority, you can provide a custom comparator when creating the PriorityQueue
. This is useful when working with objects or when sorting in descending order.
import java.util.*;
public class CustomPriorityQueueDemo {
public static void main(String[] args) {
Comparator<Integer> descending = (a, b) -> b - a;
PriorityQueue<Integer> pq = new PriorityQueue<>(descending);
pq.offer(42);
pq.offer(15);
pq.offer(30);
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // Prints: 42, 30, 15
}
}
}
📝 The comparator
(a, b) -> b - a
reverses natural ordering, making it a max-heap.
Let’s consider a real-world use case like task scheduling where tasks have priorities.
import java.util.*;
class Task {
String name;
int priority;
Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public String toString() {
return name + " (priority: " + priority + ")";
}
}
public class TaskScheduler {
public static void main(String[] args) {
Comparator<Task> byPriority = Comparator.comparingInt(t -> t.priority);
PriorityQueue<Task> taskQueue = new PriorityQueue<>(byPriority);
taskQueue.offer(new Task("Write report", 3));
taskQueue.offer(new Task("Fix bugs", 1));
taskQueue.offer(new Task("Update docs", 2));
while (!taskQueue.isEmpty()) {
System.out.println(taskQueue.poll());
}
}
}
Output:
Fix bugs (priority: 1)
Update docs (priority: 2)
Write report (priority: 3)
PriorityQueue
orders elements by natural order (Comparable
) or a custom Comparator
.peek()
method retrieves the head (highest-priority element), and poll()
removes it.Comparator
logic to ensure correct prioritization.PriorityQueue
is a versatile tool when order of processing is driven by importance rather than arrival time.
The Deque
interface in Java represents a double-ended queue — a collection that supports adding, removing, and examining elements from both ends (front and back). This flexibility makes it useful for implementing both queues (FIFO) and stacks (LIFO).
Unlike a standard Queue
, which primarily supports insertion at the tail and removal at the head, a Deque
allows operations at both the head and tail:
offerFirst()
, offerLast()
, push()
pollFirst()
, pollLast()
, pop()
peekFirst()
, peekLast()
This versatility lets you use a Deque
as:
Java provides two common Deque
implementations:
Feature | ArrayDeque |
LinkedList |
---|---|---|
Internal structure | Resizable array | Doubly linked list |
Performance | Faster for most operations due to array access | Slightly slower due to node traversal |
Null elements | Does not allow null elements |
Allows null elements |
Memory overhead | Lower overhead (array-backed) | Higher overhead (node objects with pointers) |
Thread safety | Neither are thread-safe by default | Neither are thread-safe |
ArrayDeque
is generally recommended for most use cases because of its better performance and lower memory footprint, unless you need the extra flexibility of LinkedList
(such as null
values or using it also as a List
).
Use Deque
when your application needs to:
Queue
does not support.Here is a quick rundown of important Deque
methods with examples using ArrayDeque
:
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
// Add elements at the tail (like a queue)
deque.offerLast("A"); // same as offer()
deque.offerLast("B");
deque.offerLast("C");
System.out.println(deque); // [A, B, C]
// Add element at the front
deque.offerFirst("Start");
System.out.println(deque); // [Start, A, B, C]
// Peek first and last elements
System.out.println("First: " + deque.peekFirst()); // Start
System.out.println("Last: " + deque.peekLast()); // C
// Remove from front (like dequeue)
System.out.println("Removed first: " + deque.pollFirst()); // Start
System.out.println(deque); // [A, B, C]
// Use push/pop as stack operations (LIFO)
deque.push("X"); // equivalent to offerFirst()
System.out.println(deque); // [X, A, B, C]
System.out.println("Popped: " + deque.pop()); // X
System.out.println(deque); // [A, B, C]
}
}
Deque
interface extends Queue
with support for adding/removing at both ends.ArrayDeque
is the preferred implementation for most cases due to its efficiency.LinkedList
can also serve as a Deque
but has more overhead and allows null
elements.Deque
when your program requires both FIFO and LIFO behaviors or double-ended access.Mastering Deque
opens up many flexible data structure possibilities, enabling you to solve a wide range of problems efficiently with just one interface.
Example 1: FIFO behavior using Queue (LinkedList)
import java.util.*;
public class QueueFIFOExample {
public static void main(String[] args) {
Queue<String> fifoQueue = new LinkedList<>();
// Adding elements to the queue
fifoQueue.offer("First");
fifoQueue.offer("Second");
fifoQueue.offer("Third");
System.out.println("FIFO Queue:");
// Poll elements in insertion order (FIFO)
while (!fifoQueue.isEmpty()) {
System.out.println(fifoQueue.poll());
}
// Output:
// First
// Second
// Third
}
}
Example 2: LIFO behavior using Deque (ArrayDeque as Stack)
import java.util.*;
public class DequeLIFOExample {
public static void main(String[] args) {
Deque<String> lifoStack = new ArrayDeque<>();
// Pushing elements onto the stack
lifoStack.push("Bottom");
lifoStack.push("Middle");
lifoStack.push("Top");
System.out.println("\nLIFO Stack:");
// Pop elements in reverse order of insertion (LIFO)
while (!lifoStack.isEmpty()) {
System.out.println(lifoStack.pop());
}
// Output:
// Top
// Middle
// Bottom
}
}
Example 3: PriorityQueue with custom priorities
import java.util.*;
public class PriorityQueueExample {
static class Task implements Comparable<Task> {
String name;
int priority; // Lower number = higher priority
Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
@Override
public String toString() {
return name + " (priority: " + priority + ")";
}
}
public static void main(String[] args) {
PriorityQueue<Task> taskQueue = new PriorityQueue<>();
taskQueue.offer(new Task("Low priority task", 5));
taskQueue.offer(new Task("High priority task", 1));
taskQueue.offer(new Task("Medium priority task", 3));
System.out.println("\nPriority Queue:");
// Poll tasks by priority (lowest priority number first)
while (!taskQueue.isEmpty()) {
System.out.println(taskQueue.poll());
}
// Output:
// High priority task (priority: 1)
// Medium priority task (priority: 3)
// Low priority task (priority: 5)
}
}
FIFO Queue (LinkedList): Elements are added at the tail and removed from the head, preserving insertion order. poll()
returns elements in the exact order they were added.
LIFO Stack (ArrayDeque): Using push()
and pop()
methods on a Deque
treats it as a stack, so the last inserted element is the first to be removed, demonstrating Last-In-First-Out behavior.
PriorityQueue: Elements are ordered by their priority, defined here by the Comparable
implementation in Task
. The queue always returns the element with the highest priority (lowest number) first, regardless of insertion order.
These examples show how different queue and deque implementations can be used to solve diverse real-world problems based on ordering and priority requirements.