How to Implement Queue in Java using Array and Generics?


The queue is an important data structure that operates on the principle of First-In-First-Out (FIFO), where the element entered first is removed first. For Java, creating a queue through an array and using generics can provide adaptability and versatility. By incorporating generics, we can create a queue housing any kind of data type with ease, while leveraging an array creates a contained space for memory-efficient storage.

Combining various features helps in designing a robust queue. This article discusses how an array and generics can be used to implement a queue in Java while exploring underlying concepts and code structures.

Array

An array is a type of data structure used in Java programming to store a sequence of elements that are similar in nature and size. This data structure makes it effortless to manage and access a collection of values efficiently under one variable name. In Java, declaring arrays follows this syntax:

datatype[] arrayName;

When working with arrays, the "datatype" specifies the type of elements that will be stored in the array, like int or String. Meanwhile, to declare an array variable, one should include square brackets [] after the variable name.

Once the array is declared in Java, it can be initialized and assigned values using different methods except for changing its length, which is fixed and defined at creation. Some initialization options include utilizing the new operator or initiating it with predefined elements.

Approaches

There are multiple approaches to implementing a queue in Java using an array and generics. Let's explore two common methods:

  • Fixed-Size Array Approach

  • Dynamic Resizing Array Approach

Fixed-Size Array Approach

In this approach, the queue's elements are stored in an array of fixed size. When enqueuing, the element is added to the back index after incrementing it. Dequeuing involves incrementing the front index and returning the element.

However, if additional enqueue operations result in a number of elements equal to the array's size, an exception is thrown since the queue is deemed full at that point. This technique provides a simple implementation but comes with its limitation as it restricts how many elements can be contained - which cannot surpass the maximum size of the array.

Algorithm

  • Initialize the queue with a fixed-size array, front index, rear index, and size counter.

  • Enqueue:

  • Check if the queue is full.

    • If it is not full, increment the rear index and add the element at that position.

    • Update the size counter.

  • Dequeue:

    • Check if the queue is empty.

    • If it is not empty, access the element at the front index.

    • Increment the front index and update the size counter.

  • Return the element.

    • Peek: Return the element at the front index without removing it.

  • Check if the queue is empty by comparing the size counter to zero.

  • Check if the queue is full by comparing the size counter to the array size.

  • Get the size of the queue by returning the size counter.

Example

import java.util.NoSuchElementException;
public class FixedSizeArrayQueue<T> {
   private T[] queueArray;
   private int front;
   private int rear;
   private int size;
   
   public FixedSizeArrayQueue(int capacity) {
      queueArray = (T[]) new Object[capacity];
      front = 0;
      rear = -1;
      size = 0;
   }
   
   public void enqueue(T item) {
      if (isFull()) {
         throw new IllegalStateException("Queue is full. Cannot enqueue item.");
      }
      
      rear = (rear + 1) % queueArray.length;
      queueArray[rear] = item;
      size++;
   }
   
   public T dequeue() {
      if (isEmpty()) {
         throw new NoSuchElementException("Queue is empty. Cannot dequeue item.");
      }
      
      T item = queueArray[front];
      front = (front + 1) % queueArray.length;
      size--;
      return item;
   }
   
   public T peek() {
      if (isEmpty()) {
         throw new NoSuchElementException("Queue is empty. Cannot peek item.");
      }
      
      return queueArray[front];
   }
   
   public boolean isEmpty() {
      return size == 0;
   }
   
   public boolean isFull() {
      return size == queueArray.length;
   }
   
   public int size() {
      return size;
   }
   
   public static void main(String[] args) {
      FixedSizeArrayQueue<String> queue = new FixedSizeArrayQueue<>(4);
      
      queue.enqueue("Apple");
      queue.enqueue("Banana");
      queue.enqueue("Cherry");
      
      System.out.println("Size of the queue: " + queue.size()); // Output: Size of the queue: 3
      System.out.println("Front element: " + queue.peek()); // Output: Front element: Apple
      
      String item1 = queue.dequeue();
      String item2 = queue.dequeue();
      
      System.out.println("Dequeued items: " + item1 + ", " + item2); // Output: Dequeued items: Apple, Banana
      
      queue.enqueue("Durian");
      
      System.out.println("Is the queue full? " + queue.isFull()); // Output: Is the queue full? false
      
      while (!queue.isEmpty()) {
         System.out.println("Dequeued item: " + queue.dequeue());
      }
      
      System.out.println("Is the queue empty? " + queue.isEmpty()); // Output: Is the queue empty? true
   }
}

Output

Size of the queue: 3
Front element: Apple
Dequeued items: Apple, Banana
Is the queue full? false
Dequeued item: Cherry
Dequeued item: Durian
Is the queue empty? true

Dynamic Resizing Array Approach

In this approach, a small-sized array is initially used, and as the queue grows and reaches the array's capacity, a new larger-sized array is created. The elements from the original array are then copied to the new array. This allows the queue to dynamically resize and accommodate a larger number of elements. Dequeueing involves incrementing the front index and returning the element, while enqueueing appends the element to the end of the array. This method provides flexibility in terms of size but incurs additional overhead due to array resizing.

Algorithm

  • Initialize the queue with a small-sized array, front index, rear index, and size counter.

  • Enqueue:

    • Check if the queue is full by comparing the size counter to the array size.

    • If it is full, create a new larger-sized array and copy the elements from the original array.

    • Increment the rear index and add the element at that position in the array.

    • Update the size counter.

  • Dequeue:

    • Check if the queue is empty by comparing the size counter to zero.

    • If it is not empty, access the element at the front index.

    • Increment the front index and update the size counter.

  • Return the element.

  • Peek: Return the element at the front index without removing it.

  • Check if the queue is empty by comparing the size counter to zero.

  • Check if the queue is full by comparing the size counter to the array size.

  • Get the size of the queue by returning the size counter.

Example

import java.util.Arrays;
import java.util.NoSuchElementException;

public class DynamicResizingArrayQueue<T> {
   private T[] queueArray;
   private int front;
   private int rear;
   private int size;
   
   public DynamicResizingArrayQueue() {
      queueArray = (T[]) new Object[4];
      front = 0;
      rear = -1;
      size = 0;
   }
   
   public void enqueue(T item) {
      if (isFull()) {
         resizeArray();
      }
      
      rear++;
      queueArray[rear] = item;
      size++;
   }
   
   public T dequeue() {
      if (isEmpty()) {
         throw new NoSuchElementException("Queue is empty. Cannot dequeue item.");
      }
      
      T item = queueArray[front];
      front++;
      size--;
      
      if (size < queueArray.length / 2) {
         shrinkArray();
      }
      
      return item;
   }
   
   public T peek() {
      if (isEmpty()) {
         throw new NoSuchElementException("Queue is empty. Cannot peek item.");
      }
      
      return queueArray[front];
   }
   
   public boolean isEmpty() {
      return size == 0;
   }
   
   public boolean isFull() {
      return size == queueArray.length;
   }
   
   public int size() {
      return size;
   }
   
   private void resizeArray() {
      int newCapacity = queueArray.length * 2;
      queueArray = Arrays.copyOf(queueArray, newCapacity);
   }
   
   private void shrinkArray() {
      int newCapacity = queueArray.length / 2;
      queueArray = Arrays.copyOfRange(queueArray, front, front + size, (Class<? extends T[]>) queueArray.getClass());
      front = 0;
      rear = size - 1;
   }
   
   public static void main(String[] args) {
      DynamicResizingArrayQueue<Integer> queue = new DynamicResizingArrayQueue<>();
      
      queue.enqueue(10);
      queue.enqueue(20);
      queue.enqueue(30);
      
      System.out.println("Size of the queue: " + queue.size()); // Output: Size of the queue: 3
      System.out.println("Front element: " + queue.peek()); // Output: Front element: 10
      
      int item1 = queue.dequeue();
      int item2 = queue.dequeue();
      
      System.out.println("Dequeued items: " + item1 + ", " + item2); // Output: Dequeued items: 10, 20
      
      queue.enqueue(40);
      queue.enqueue(50);
      queue.enqueue(60);
      
      System.out.println("Is the queue full? " + queue.isFull()); // Output: Is the queue full? false
      
      while (!queue.isEmpty()) {
         System.out.println("Dequeued item: " + queue.dequeue());
      }
      
      System.out.println("Is the queue empty? " + queue.isEmpty()); // Output: Is the queue empty? true
   }
}

Output

Size of the queue: 3
Front element: 10
Dequeued items: 10, 20
Is the queue full? true
Dequeued item: 30
Dequeued item: 40
Dequeued item: 50
Dequeued item: 60
Is the queue empty? true

Conclusion

Implementing a queue in Java using an array and generics is explained in this tutorial. It offers a flexible data structure that manages elements in FIFO order. Arrays can be used for the implementation, which are excellent for small datasets but not scalable to larger ones due to their fixed-size approach. To overcome this challenge, dynamic resizing arrays allow the queue to grow dynamically as more elements are added, thus accommodating a higher number of elements without limitations on storage capacity.

The right approach depends on specific application requirements, necessitating trade-offs between fixed-size constraints and dynamic resizing capabilities.

Updated on: 27-Jul-2023

930 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements