Understanding Java Queue Interface

The Java Queue interface helps you manage data. It stores items in a specific order. You can use it to process things one by one. This guide shows you how to use the Queue interface in Java. You will learn its benefits and see how to implement it.

What is the Java Queue Interface?

A Java Queue is a collection designed for holding elements prior to processing. It follows a specific order. This order is usually First-In, First-Out (FIFO). This means the first element added is the first one removed.

The Queue interface is part of the Java Collections Framework. It provides methods to add, remove, and inspect elements. Many data structures implement the Queue interface. These include LinkedList and PriorityQueue.

Why Use the Java Queue Interface?

Using the Queue interface helps you manage tasks. It ensures fair processing. Here are key reasons to use it:

  • Orderly Processing: Queue guarantees elements are processed in a defined order. This is typically FIFO.
  • Task Management: You can use a Queue to manage tasks in an application. For example, a print spooler uses a Queue.
  • Resource Sharing: It helps control access to shared resources. You can add requests to a queue and process them one by one.
  • Concurrency: Queue is useful in multi-threaded environments. It helps manage data flow between threads.
  • Buffering: You can use it to buffer data streams. This smooths out data flow.

Learn Java from scratch with this course, covering everything from fundamentals like variables and control structures to advanced object-oriented programming concepts such as classes, inheritance, and polymorphism.

Core Methods of the Java Queue Interface

The Queue interface defines several methods. These methods help you interact with the queue. They fall into two categories: those that throw exceptions and those that return special values.

Here are the main methods:

  • add(E e): This method inserts an element e into the queue. It throws an IllegalStateException if the queue is full.
  • offer(E e): This method also inserts an element e into the queue. It returns true if the element was added successfully. It returns false if the queue is full. Use offer() to avoid exceptions when the queue has a capacity limit.
  • remove(): This method retrieves and removes the head of the queue. It throws a NoSuchElementException if the queue is empty.
  • poll(): This method also retrieves and removes the head of the queue. It returns null if the queue is empty. Use poll() to avoid exceptions when the queue might be empty.
  • element(): This method retrieves the head of the queue without removing it. It throws a NoSuchElementException if the queue is empty.
  • peek(): This method retrieves the head of the queue without removing it. It returns null if the queue is empty. Use peek() to avoid exceptions when the queue might be empty.

Here is a table summarizing these methods:

MethodThrows Exception if FailsReturns Special Value if FailsPurpose
add(e)Yes (IllegalStateException)NoInserts element
offer(e)NofalseInserts element
remove()Yes (NoSuchElementException)NoRemoves head of queue
poll()NonullRemoves head of queue
element()Yes (NoSuchElementException)NoRetrieves head of queue (does not remove)
peek()NonullRetrieves head of queue (does not remove)

Implementations of the Java Queue Interface

The Queue interface is an abstraction. You cannot create an object directly from it. You must use a concrete class that implements Queue. Java provides several such classes. Each has unique characteristics.

1. LinkedList

LinkedList is a common implementation of the Queue interface. It implements both List and Deque interfaces. This means it can function as a queue or a double-ended queue.

  • Flexibility: LinkedList supports both queue and stack operations.
  • Dynamic Size: It can grow or shrink as needed.
  • Good for General Use: It works well for most queueing needs.

Here is how you can use LinkedList as a Queue:

import java.util.LinkedList;
import java.util.Queue;

public class LinkedListQueueExample {
    public static void main(String[] args) {
        // Create a Queue using LinkedList
        Queue<String> messages = new LinkedList<>();

        // Add elements to the queue
        messages.add("First message");
        messages.offer("Second message"); // Use offer to avoid exceptions if queue is capacity-limited
        messages.add("Third message");

        System.out.println("Queue: " + messages); // Output: Queue: [First message, Second message, Third message]

        // Inspect the head of the queue
        String head = messages.peek();
        System.out.println("Head of queue (peek): " + head); // Output: Head of queue (peek): First message

        // Remove elements from the queue
        String removedMessage = messages.remove();
        System.out.println("Removed message: " + removedMessage); // Output: Removed message: First message
        System.out.println("Queue after remove: " + messages); // Output: Queue after remove: [Second message, Third message]

        String polledMessage = messages.poll();
        System.out.println("Polled message: " + polledMessage); // Output: Polled message: Second message
        System.out.println("Queue after poll: " + messages); // Output: Queue after poll: [Third message]

        // Try to poll from an empty queue
        messages.poll(); // Removes "Third message"
        System.out.println("Queue after polling last element: " + messages); // Output: Queue after polling last element: []
        String emptyPoll = messages.poll();
        System.out.println("Polled from empty queue: " + emptyPoll); // Output: Polled from empty queue: null
    }
}

2. PriorityQueue

PriorityQueue is another implementation of the Queue interface. It processes elements based on their priority. It does not follow the strict FIFO order. Elements are ordered based on their natural ordering or a custom Comparator.

  • Priority-Based Ordering: Elements with higher priority are processed first.
  • Heap Data Structure: PriorityQueue uses a min-heap internally. This ensures fast retrieval of the highest-priority element.
  • No Null Elements: It does not allow null elements.

Here is how you can use PriorityQueue:

import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // Create a PriorityQueue
        Queue<Integer> numbers = new PriorityQueue<>();

        // Add elements to the queue
        numbers.add(50);
        numbers.add(10);
        numbers.add(30);
        numbers.offer(20);

        System.out.println("PriorityQueue: " + numbers); // Order may vary due to heap structure, e.g., [10, 20, 30, 50] or [10, 20, 50, 30]

        // Retrieve and remove elements (smallest element comes first)
        System.out.println("Removed: " + numbers.poll()); // Output: Removed: 10
        System.out.println("PriorityQueue: " + numbers); // Output will show the next smallest element at the head

        System.out.println("Removed: " + numbers.poll()); // Output: Removed: 20
        System.udt.println("PriorityQueue: " + numbers); // Output will show the next smallest element at the head

        System.out.println("Peek: " + numbers.peek()); // Output: Peek: 30
    }
}

You can also define custom priority using a Comparator:

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

public class CustomPriorityQueueExample {
    public static void main(String[] args) {
        // Create a PriorityQueue with a custom Comparator (descending order)
        Queue<Integer> numbers = new PriorityQueue<>(Comparator.reverseOrder());

        // Add elements
        numbers.add(50);
        numbers.add(10);
        numbers.add(30);
        numbers.offer(20);

        System.out.println("PriorityQueue (Custom Order): " + numbers); // Order may vary due to heap structure

        // Retrieve and remove elements (largest element comes first)
        System.out.println("Removed: " + numbers.poll()); // Output: Removed: 50
        System.out.println("Removed: " + numbers.poll()); // Output: Removed: 30
    }
}

3. ArrayDeque

ArrayDeque implements the Deque interface. Deque stands for Double-Ended Queue. It means you can add or remove elements from both ends. ArrayDeque can be used as a queue or a stack. It is generally faster than LinkedList for adding and removing elements at both ends.

  • Faster Operations: It often performs better than LinkedList for typical queue operations.
  • No Capacity Restrictions (mostly): It dynamically resizes itself.
  • No Null Elements: It does not allow null elements.

Here is how you can use ArrayDeque as a Queue:

import java.util.ArrayDeque;
import java.util.Queue;

public class ArrayDequeQueueExample {
    public static void main(String[] args) {
        // Create a Queue using ArrayDeque
        Queue<String> tasks = new ArrayDeque<>();

        // Add elements
        tasks.add("Task 1");
        tasks.offer("Task 2");
        tasks.add("Task 3");

        System.out.println("Queue: " + tasks); // Output: Queue: [Task 1, Task 2, Task 3]

        // Inspect and remove
        System.out.println("Peek: " + tasks.peek()); // Output: Peek: Task 1
        System.out.println("Poll: " + tasks.poll()); // Output: Poll: Task 1
        System.out.println("Queue after poll: " + tasks); // Output: Queue after poll: [Task 2, Task 3]
    }
}

4. ConcurrentLinkedQueue

ConcurrentLinkedQueue is a thread-safe implementation of the Queue interface. It is part of the java.util.concurrent package. Use it when multiple threads access the same queue.

  • Thread-Safe: Multiple threads can add and remove elements safely without external synchronization.
  • Non-Blocking: Operations do not block threads.
  • Unlimited Capacity: It can grow as needed.

Here is how you can use ConcurrentLinkedQueue:

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentQueueExample {
    public static void main(String[] args) {
        // Create a thread-safe queue
        Queue<String> sharedQueue = new ConcurrentLinkedQueue<>();

        // Producer thread
        Runnable producer = () -> {
            for (int i = 0; i < 5; i++) {
                String item = "Item " + i;
                sharedQueue.offer(item);
                System.out.println("Produced: " + item);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        };

        // Consumer thread
        Runnable consumer = () -> {
            for (int i = 0; i < 5; i++) {
                String item = sharedQueue.poll();
                if (item != null) {
                    System.out.println("Consumed: " + item);
                } else {
                    System.out.println("Queue is empty, waiting...");
                }
                try {
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        };

        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

5. LinkedBlockingQueue

LinkedBlockingQueue is another thread-safe queue. It is also part of the java.util.concurrent package. It is a bounded queue, meaning it can have a maximum capacity. It also provides blocking operations.

  • Thread-Safe: It supports safe concurrent access.
  • Blocking Operations: put() and take() methods block if the queue is full or empty. This helps manage flow control.
  • Bounded Capacity: You can set a maximum size for the queue.

Here is how you can use LinkedBlockingQueue:

import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) {
        // Create a blocking queue with a capacity of 2
        LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(2);

        // Producer thread
        Runnable producer = () -> {
            try {
                blockingQueue.put("Data 1");
                System.out.println("Producer added: Data 1");
                blockingQueue.put("Data 2");
                System.out.println("Producer added: Data 2");
                blockingQueue.put("Data 3"); // This will block until consumer takes an item
                System.out.println("Producer added: Data 3");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };

        // Consumer thread
        Runnable consumer = () -> {
            try {
                Thread.sleep(500); // Give producer some time to add
                String item1 = blockingQueue.take();
                System.out.println("Consumer took: " + item1);
                String item2 = blockingQueue.take();
                System.out.println("Consumer took: " + item2);
                String item3 = blockingQueue.take();
                System.out.println("Consumer took: " + item3);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };

        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

Best Practices for Using Java Queues

Use these tips to make your Java queue code better:

  • Choose the Right Implementation:
    • For general-purpose FIFO queues, use LinkedList or ArrayDeque. ArrayDeque is often faster.
    • For priority-based processing, use PriorityQueue.
    • For concurrent applications, use ConcurrentLinkedQueue or LinkedBlockingQueue.
  • Understand Method Behavior: Know the difference between methods that throw exceptions (add, remove, element) and those that return special values (offer, poll, peek). Use offer, poll, and peek to avoid unexpected errors, especially when dealing with capacity-limited queues or potentially empty queues.
  • Handle NoSuchElementException and IllegalStateException: If you use add(), remove(), or element(), wrap them in try-catch blocks to handle exceptions gracefully.
  • Consider Thread Safety: Always use concurrent queue implementations (like ConcurrentLinkedQueue or LinkedBlockingQueue) when multiple threads access the queue. Regular LinkedList or ArrayDeque are not thread-safe.
  • Manage Capacity: If your queue has a fixed size, use a BlockingQueue implementation. This helps prevent OutOfMemoryError and provides flow control.
  • Avoid Null Elements: Most Queue implementations do not allow null elements. Adding null often results in a NullPointerException.
  • Iterate Carefully: When iterating over a Queue, do not modify it during iteration unless you use a ConcurrentQueue. Modifying a non-thread-safe queue during iteration can lead to ConcurrentModificationException.

Common Use Cases for Java Queues

Java queues are used in many real-world scenarios:

  • Task Scheduling: Operating systems use queues to manage processes. Web servers use them to handle client requests.
  • Producer-Consumer Problem: One thread produces data, and another thread consumes it. A queue acts as a buffer between them. This is common in message passing systems.
  • Breadth-First Search (BFS): Graph traversal algorithms use queues to explore nodes level by level.
  • Message Queues: Systems like Apache Kafka or RabbitMQ use queueing concepts for reliable message delivery.
  • Print Spoolers: Documents are added to a print queue. The printer processes them in order.
  • Event Handling: Applications use queues to process events in the order they occur.
Avatar photo
Great Learning Editorial Team
The Great Learning Editorial Staff includes a dynamic team of subject matter experts, instructors, and education professionals who combine their deep industry knowledge with innovative teaching methods. Their mission is to provide learners with the skills and insights needed to excel in their careers, whether through upskilling, reskilling, or transitioning into new fields.

Academy Pro Subscription

Grab 50% off
on Top Courses - Free Trial Available

×
Scroll to Top