Java Collections Framework: Internal Implementation


The Java Collections Framework provides a unified architecture for managing collections such as lists, sets, maps, and queues. This article explores the internal implementation of key collection classes, their underlying data structures, time complexities, and practical examples to demonstrate their usage. The focus is on the List, Set, Map, and Queue interfaces, their primary implementations, and their suitability for various use cases.


1. List Interface represents an ordered collection that allows duplicates. Its main implementations are ArrayList, LinkedList, and Vector.


1.1 ArrayList

  • Internal Implementation: Backed by a dynamic array (resizable array). The array starts with a default capacity of 10 and grows by approximately 1.5x when full, copying elements to a new array.
  • Key Characteristics: 🔵 Fast random access and iteration (O(1)). 🔵Slow for insertions/deletions in the middle (O(n) due to element shifting). 🔵Not thread-safe (use Collections.synchronizedList or CopyOnWriteArrayList for thread safety).
  • Time Complexities: 🔵Access (get/set): O(1) 🔵Insertion/Deletion at end: O(1) 🔵 amortizedInsertion/Deletion at specific index: O(n)

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        // Adding elements (O(1) amortized)
        list.add("Apple"); // [Apple]
        list.add("Banana"); // [Apple, Banana]
        list.add(1, "Orange"); // [Apple, Orange, Banana] - Shifts elements (O(n))

        // Accessing elements (O(1))
        System.out.println("Element at index 1: " + list.get(1)); // Output: Orange

        // Removing element (O(n) due to shifting)
        list.remove(0); // [Orange, Banana]
        System.out.println("List after removal: " + list); // Output: [Orange, Banana]
    }
}        

  • Use Case: Ideal for scenarios requiring frequent random access or iteration, such as storing a list of items.

1.2 LinkedList

  • Internal Implementation: Backed by a doubly-linked list, where each node contains data and references to the previous and next nodes.
  • Key Characteristics: 🔵 Fast for insertions/deletions at the ends (O(1)). 🔵 Slow for random access or middle operations (O(n) due to traversal). 🔵 Not thread-safe.
  • Time Complexities: 🔵 Access (get/set): O(n) 🔵 Insertion/Deletion at beginning/end: O(1) 🔵 Insertion/Deletion at specific index: O(n)

import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        // Adding elements (O(1) at ends)
        list.addFirst("Apple"); // [Apple]
        list.addLast("Banana"); // [Apple, Banana]
        list.add(1, "Orange"); // [Apple, Orange, Banana] - Traverses to index (O(n))

        // Accessing element (O(n) due to traversal)
        System.out.println("Element at index 1: " + list.get(1)); // Output: Orange

        // Removing element (O(1) at ends)
        list.removeFirst(); // [Orange, Banana]
        System.out.println("List after removal: " + list); // Output: [Orange, Banana]
    }
}        

  • Use Case: Suitable for frequent additions/removals at the ends, such as implementing a queue or stack.

1.3 Vector

  • Internal Implementation: Backed by a dynamic array, similar to ArrayList, but all methods are synchronized for thread safety.
  • Key Characteristics: 🔵 Thread-safe but slower due to synchronization overhead. 🔵 Grows by doubling the array size (unlike ArrayList’s 1.5x). 🔵 Rarely used; prefer ArrayList with synchronization wrappers.
  • Time Complexities: Same as ArrayList, with synchronization overhead.

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        Vector<String> vector = new Vector<>();
        vector.add("Apple"); // Synchronized (O(1) amortized)
        vector.add("Banana");
        System.out.println("Vector: " + vector); // Output: [Apple, Banana]

        // Thread-safe operation
        new Thread(() -> vector.add("Orange")).start();
        // Eventually: [Apple, Banana, Orange]
    }
}        

  • Use Case: Used in legacy code or when thread safety is needed without modern concurrent collections.


2. Set Interface represents a collection with no duplicates. Its main implementations are HashSet, LinkedHashSet, and TreeSet.

2.1 HashSet

  • Internal Implementation: Backed by a HashMap, where elements are stored as keys with a dummy value (PRESENT). Uses a hash table with buckets (linked lists or red-black trees for collisions in Java 8+).
  • Key Characteristics: 🔵 Fast lookups, additions, and removals (O(1) average). 🔵 No order guarantee. 🔵 Not thread-safe (use Collections.synchronizedSet or CopyOnWriteArraySet).
  • Time Complexities: 🔵 Add/Remove/Contains: O(1) average, O(n) worst case (collisions).

import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        // Adding elements (O(1) average)
        set.add("Apple"); // Hashed to a bucket
        set.add("Banana");
        set.add("Apple"); // Duplicate ignored
        System.out.println("Set: " + set); // Output: [Apple, Banana] (order not guaranteed)

        // Checking containment (O(1) average)
        System.out.println("Contains Banana? " + set.contains("Banana")); // Output: true

        // Removing element (O(1) average)
        set.remove("Apple");
        System.out.println("Set after removal: " + set); // Output: [Banana]
    }
}        

  • Use Case: Ideal for fast duplicate checking, such as unique user IDs.

2.2 LinkedHashSet

  • Internal Implementation: Extends HashSet, backed by a LinkedHashMap to maintain insertion order.
  • Key Characteristics: 🔵 Preserves insertion order. 🔵 Slightly slower than HashSet due to linked list overhead. 🔵 Not thread-safe.
  • Time Complexities: 🔵 Same as HashSet, with minor overhead.

import java.util.LinkedHashSet;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");
        System.out.println("Set: " + set); // Output: [Apple, Banana, Orange] (insertion order)

        set.remove("Banana");
        System.out.println("Set after removal: " + set); // Output: [Apple, Orange]
    }
}        

  • Use Case: Used when the order of insertion matters, such as logging events.

2.3 TreeSet

  • Internal Implementation: Backed by a red-black tree (self-balancing binary search tree). Elements are sorted by natural ordering or a custom Comparator.
  • Key Characteristics: 🔵 Maintains sorted order. 🔵 Slower than HashSet (O(log n)). 🔵 Not thread-safe (use ConcurrentSkipListSet for thread safety).
  • Time Complexities: 🔵 Add/Remove/Contains: O(log n)

import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("Banana");
        set.add("Apple");
        set.add("Orange");
        System.out.println("Set: " + set); // Output: [Apple, Banana, Orange] (sorted order)

        // Range operations
        System.out.println("Elements >= Banana: " + set.tailSet("Banana")); // Output: [Banana, Orange]
    }
}        

  • Use Case: Suitable for sorted collections, such as a leaderboard.


3. Map Interface stores key-value pairs with unique keys. Its main implementations are HashMap, LinkedHashMap, TreeMap, and ConcurrentHashMap.

3.1 HashMap

  • Internal Implementation: Backed by a hash table. Keys are hashed to buckets (linked lists or red-black trees for collisions in Java 8+).
  • Key Characteristics: 🔵 Fast key-based operations (O(1) average). 🔵 No order guarantee. 🔵 Not thread-safe (use Collections.synchronizedMap or ConcurrentHashMap).
  • Time Complexities: 🔵Put/Get/Remove: O(1) average, O(n) worst case.

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        // Adding key-value pairs (O(1) average)
        map.put("Apple", 1);
        map.put("Banana", 2);
        map.put("Apple", 3); // Overwrites value
        System.out.println("Map: " + map); // Output: {Apple=3, Banana=2} (order not guaranteed)

        // Retrieving value (O(1) average)
        System.out.println("Value for Banana: " + map.get("Banana")); // Output: 2

        // Removing key (O(1) average)
        map.remove("Apple");
        System.out.println("Map after removal: " + map); // Output: {Banana=2}
    }
}        

  • Use Case: Ideal for fast key-value lookups, such as a cache.

3.2 LinkedHashMap

  • Internal Implementation: Extends HashMap, adds a doubly-linked list to maintain insertion order or access order.
  • Key Characteristics: 🔵 Preserves insertion or access order. 🔵 Slightly slower than HashMap. 🔵 Not thread-safe.
  • Time Complexities: 🔵 Same as HashMap, with minor overhead.

import java.util.LinkedHashMap;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);
        map.put("Orange", 3);
        System.out.println("Map: " + map); // Output: {Apple=1, Banana=2, Orange=3} (insertion order)

        // Access order example
        LinkedHashMap<String, Integer> accessOrderMap = new LinkedHashMap<>(16, 0.75f, true);
        accessOrderMap.put("Apple", 1);
        accessOrderMap.put("Banana", 2);
        accessOrderMap.get("Apple"); // Access moves Apple to end
        System.out.println("Access order: " + accessOrderMap); // Output: {Banana=2, Apple=1}
    }
}        

  • Use Case: Used for LRU caches or maintaining insertion order.

3.3 TreeMap

  • Internal Implementation: Backed by a red-black tree. Keys are sorted by natural ordering or a custom Comparator.
  • Key Characteristics: 🔵 Maintains sorted order of keys. 🔵 Slower than HashMap (O(log n)). 🔵 Not thread-safe (use ConcurrentSkipListMap).
  • Time Complexities: 🔵 Put/Get/Remove: O(log n)

import java.util.TreeMap;

public class TreeMapExample {
    public static void main(String[] args) {
        TreeMap<String, Integer> map = new TreeMap<>();
        map.put("Banana", 2);
        map.put("Apple", 1);
        map.put("Orange", 3);
        System.out.println("Map: " + map); // Output: {Apple=1, Banana=2, Orange=3} (sorted by keys)

        // Range operations
        System.out.println("Keys >= Banana: " + map.tailMap("Banana")); // Output: {Banana=2, Orange=3}
    }
}        

  • Use Case: Suitable for sorted key-value pairs, such as a dictionary.

3.4 ConcurrentHashMap

  • Internal Implementation: Backed by a hash table with fine-grained locking or CAS (Compare-And-Swap) in Java 8+ for thread safety.
  • Key Characteristics: 🔵 Thread-safe, designed for high concurrency. 🔵 No full locking (unlike Hashtable). 🔵 Weakly consistent iterators.
  • Time Complexities: 🔵 Similar to HashMap, with synchronization overhead.

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);

        // Thread-safe operations
        new Thread(() -> map.put("Orange", 3)).start();
        System.out.println("Map: " + map); // Eventually: {Apple=1, Banana=2, Orange=3}

        // Atomic operation
        map.putIfAbsent("Apple", 4); // Ignored (key exists)
        System.out.println("Map after putIfAbsent: " + map);
    }
}        

  • Use Case: Used in multi-threaded applications, such as shared caches.


4. Queue and Deque Interfaces support FIFO (First-In-First-Out) and LIFO (Last-In-First-Out) operations. Key implementations are PriorityQueue, ArrayDeque, and ConcurrentLinkedQueue.

4.1 PriorityQueue

  • Internal Implementation: Backed by a min-heap (array-based binary heap). Elements are ordered by priority (natural ordering or Comparator).
  • Key Characteristics: 🔵 Maintains priority order, not FIFO. 🔵 Not thread-safe (use PriorityBlockingQueue for concurrency).
  • Time Complexities:Offer/Poll: 🔵 O(log n)Peek: O(1)

import java.util.PriorityQueue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        queue.offer(3); // [3]
        queue.offer(1); // [1, 3]
        queue.offer(2); // [1, 2, 3]
        System.out.println("Queue: " + queue); // Output: [1, 2, 3] (heap order)

        // Remove smallest element (O(log n))
        System.out.println("Polled: " + queue.poll()); // Output: 1
        System.out.println("Queue after poll: " + queue); // Output: [2, 3]
    }
}        

  • Use Case: Ideal for priority-based task scheduling.

4.2 ArrayDeque

  • Internal Implementation: Backed by a circular array, supporting efficient operations at both ends.
  • Key Characteristics: 🔵 Fast for queue and stack operations (O(1)). 🔵 Not thread-safe.
  • Time Complexities: 🔵 Add/Remove: O(1) amortized

import java.util.ArrayDeque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        ArrayDeque<String> deque = new ArrayDeque<>();
        deque.addFirst("Apple"); // [Apple]
        deque.addLast("Banana"); // [Apple, Banana]
        System.out.println("Deque: " + deque); // Output: [Apple, Banana]

        // Remove from front (O(1))
        System.out.println("Polled: " + deque.pollFirst()); // Output: Apple
        System.out.println("Deque after poll: " + deque); // Output: [Banana]
    }
}        

  • Use Case: Used for queue or stack operations, such as undo history.

4.3 ConcurrentLinkedQueue

  • Internal Implementation: Backed by a lock-free linked list using CAS for thread safety.
  • Key Characteristics: 🔵 Thread-safe, unbounded queue. 🔵 High concurrency with no blocking.
  • Time Complexities: 🔵 Offer/Poll: O(1) average

import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueExample {
    public static void main(String[] args) {
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        queue.offer("Apple");
        queue.offer("Banana");
        System.out.println("Queue: " + queue); // Output: [Apple, Banana]

        // Thread-safe poll
        new Thread(() -> System.out.println("Polled: " + queue.poll())).start();
        // Eventually: [Banana]
    }
}        

  • Use Case: Used in concurrent producer-consumer scenarios.


5. For multi-threaded applications, Java provides thread-safe collections in the java.util.concurrent package:

  • CopyOnWriteArrayList: Creates a new array copy on write operations, ideal for read-heavy scenarios.
  • ConcurrentHashMap: Uses fine-grained locking or CAS for high concurrency.
  • ConcurrentSkipListSet/SkipListMap: Backed by a skip list, providing sorted, thread-safe operations (O(log n)).
  • BlockingQueue (e.g., ArrayBlockingQueue, LinkedBlockingQueue): Thread-safe queues with blocking operations for producer-consumer patterns.


6. Summary of Use Cases and Performance

Article content
Collection usecases and performance



7. Key Notes

  • Thread Safety: Most collections (ArrayList, HashMap, etc.) are not thread-safe. Use Collections.synchronizedX or concurrent collections for multi-threaded applications.
  • Java 8+ Enhancements: HashMap and HashSet use red-black trees for high-collision buckets, improving worst-case performance from O(n) to O(log n). ConcurrentHashMap uses fine-grained locking and CAS for better concurrency.
  • Choosing a Collection: Use ArrayList for general-purpose lists. Use LinkedList for queue/stack operations. Use HashSet/HashMap for fast lookups. Use TreeSet/TreeMap for sorted data. Use ConcurrentHashMap or ConcurrentLinkedQueue for concurrent applications.


#JavaCollections #JavaProgramming #CollectionsFramework #JavaDevelopment #DataStructures #JavaTutorial #ArrayList #HashMap #TreeSet #ConcurrentHashMap #JavaCode #Programming #SoftwareDevelopment #JavaExamples #CodingTips #LearnJava #TechTutorials #JavaPerformance #DataStructuresAndAlgorithms #JavaConcurrency

Why even mentioning Vector? Lagacy ... at best... do not use it anymore for about 15 years+ If you need a thread-safe variant use CopyOnWriteArrayList instead...

To view or add a comment, sign in

More articles by Basuki Nath

Others also viewed

Explore content categories