Java Academy

Master Java Development

Menu

Core Java

Servlets

JDBC

Microservices

Build Tools

APIs

Core Java · Lesson 12

Collections Framework

Collections: Organizing and Managing Data

The Java Collections Framework is a unified architecture for storing, retrieving, and manipulating groups of objects. Instead of using arrays (fixed size) or rolling your own data structures, you use built-in collections optimized for different purposes. Collections include Lists (ordered, duplicates allowed), Sets (unique elements), Maps (key-value pairs), and Queues (ordered for retrieval). Mastering collections is essential — you'll use them constantly.

Lists: Ordered Collections with Duplicates

A List maintains insertion order and allows duplicates. ArrayList is a resizable array (fast random access); LinkedList is a doubly-linked list (fast insertion/deletion at ends). Use ArrayList for most cases; use LinkedList if you frequently insert/delete at the beginning.

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ListExample {
    public static void main(String[] args) {
        // ArrayList: fast random access
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        fruits.add("Apple");  // Duplicates allowed
        
        System.out.println("ArrayList: " + fruits);
        System.out.println("First fruit: " + fruits.get(0));
        System.out.println("Contains Banana: " + fruits.contains("Banana"));
        
        // LinkedList: efficient insertion/deletion at ends
        List<Integer> numbers = new LinkedList<>();
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);
        numbers.addFirst(5);   // Insert at beginning
        numbers.addLast(40);   // Insert at end
        
        System.out.println("LinkedList: " + numbers);
        
        // Iteration
        System.out.println("Iterating:");
        for (String fruit : fruits) {
            System.out.println("  " + fruit);
        }
        
        // Remove
        fruits.remove("Apple");  // Removes first occurrence
        System.out.println("After removing Apple: " + fruits);
    }
}

Sets: Unique Elements Only

A Set contains only unique elements (no duplicates). HashSet offers fast lookups (unordered); TreeSet keeps elements sorted; LinkedHashSet maintains insertion order. Use HashSet for general purposes, TreeSet when you need sorting.

import java.util.HashSet;
import java.util.TreeSet;
import java.util.LinkedHashSet;
import java.util.Set;

public class SetExample {
    public static void main(String[] args) {
        // HashSet: fast, unordered
        Set<String> uniqueFruits = new HashSet<>();
        uniqueFruits.add("Apple");
        uniqueFruits.add("Banana");
        uniqueFruits.add("Cherry");
        uniqueFruits.add("Apple");  // Duplicate: ignored
        
        System.out.println("HashSet: " + uniqueFruits);  // Order unpredictable
        System.out.println("Size: " + uniqueFruits.size());  // 3
        
        // TreeSet: sorted
        Set<Integer> sortedNumbers = new TreeSet<>();
        sortedNumbers.add(30);
        sortedNumbers.add(10);
        sortedNumbers.add(20);
        sortedNumbers.add(10);  // Duplicate: ignored
        
        System.out.println("TreeSet: " + sortedNumbers);  // [10, 20, 30]
        
        // LinkedHashSet: insertion order maintained
        Set<String> linkedFruits = new LinkedHashSet<>();
        linkedFruits.add("Mango");
        linkedFruits.add("Grape");
        linkedFruits.add("Pear");
        
        System.out.println("LinkedHashSet: " + linkedFruits);
    }
}

Maps: Key-Value Associations

A Map stores key-value pairs. HashMap is fast and unordered; TreeMap keeps keys sorted; LinkedHashMap maintains insertion order. Use HashMap for lookups; use TreeMap if you need keys in sorted order.

import java.util.HashMap;
import java.util.TreeMap;
import java.util.Map;

public class MapExample {
    public static void main(String[] args) {
        // HashMap: fast lookups, unordered
        Map<String, Integer> studentScores = new HashMap<>();
        studentScores.put("Alice", 95);
        studentScores.put("Bob", 87);
        studentScores.put("Charlie", 92);
        
        System.out.println("HashMap: " + studentScores);
        System.out.println("Alice's score: " + studentScores.get("Alice"));
        System.out.println("Contains Bob: " + studentScores.containsKey("Bob"));
        
        // TreeMap: sorted by keys
        Map<String, String> sortedCountries = new TreeMap<>();
        sortedCountries.put("India", "New Delhi");
        sortedCountries.put("Brazil", "Brasília");
        sortedCountries.put("Canada", "Ottawa");
        
        System.out.println("TreeMap: " + sortedCountries);  // Sorted by key
        
        // Iteration
        System.out.println("All scores:");
        for (String name : studentScores.keySet()) {
            System.out.println("  " + name + ": " + studentScores.get(name));
        }
        
        // Iteration with entrySet (more efficient)
        System.out.println("Using entrySet:");
        for (Map.Entry<String, Integer> entry : studentScores.entrySet()) {
            System.out.println("  " + entry.getKey() + ": " + entry.getValue());
        }
        
        // Remove
        studentScores.remove("Bob");
        System.out.println("After removing Bob: " + studentScores);
    }
}

Queues and Deques: Sequential Access

A Queue maintains FIFO (First-In-First-Out) order — elements are added at the end and removed from the front. A Deque (double-ended queue) allows insertion/removal at both ends. Use queues for tasks needing sequential processing; use deques for flexible access patterns.

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

public class QueueExample {
    public static void main(String[] args) {
        // Queue: FIFO
        Queue<String> taskQueue = new LinkedList<>();
        taskQueue.offer("Task 1");
        taskQueue.offer("Task 2");
        taskQueue.offer("Task 3");
        
        System.out.println("Queue: " + taskQueue);
        System.out.println("Next task: " + taskQueue.poll());  // Remove from front
        System.out.println("Peek: " + taskQueue.peek());       // View front without removing
        
        // Deque: double-ended
        Deque<Integer> deque = new LinkedList<>();
        deque.addFirst(1);
        deque.addLast(2);
        deque.addFirst(0);
        
        System.out.println("Deque: " + deque);
        System.out.println("Remove first: " + deque.removeFirst());
        System.out.println("Remove last: " + deque.removeLast());
    }
}

Common Collection Algorithms

Java provides utility methods for common operations: sorting, searching, reversing, shuffling. The Collections class (note: singular "Collection" for interface, plural for utility) provides these static methods.

import java.util.Collections;
import java.util.ArrayList;
import java.util.List;

public class CollectionAlgorithms {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        numbers.add(9);
        
        System.out.println("Original: " + numbers);
        
        // Sort
        Collections.sort(numbers);
        System.out.println("Sorted: " + numbers);
        
        // Reverse
        Collections.reverse(numbers);
        System.out.println("Reversed: " + numbers);
        
        // Shuffle
        Collections.shuffle(numbers);
        System.out.println("Shuffled: " + numbers);
        
        // Binary search (requires sorted list)
        Collections.sort(numbers);
        int index = Collections.binarySearch(numbers, 8);
        System.out.println("Index of 8: " + index);
        
        // Max and min
        System.out.println("Max: " + Collections.max(numbers));
        System.out.println("Min: " + Collections.min(numbers));
        
        // Frequency
        List<String> words = new ArrayList<>();
        words.add("java");
        words.add("python");
        words.add("java");
        System.out.println("'java' appears: " + Collections.frequency(words, "java") + " times");
    }
}
CollectionOrderedDuplicatesNull ValuesPerformance
ArrayList✓✓✓Fast access, O(1)
LinkedList✓✓✓Fast insert/delete at ends
HashSet✗✗✓Fast lookup, O(1)
TreeSet✓✗✗Sorted, O(log n)
HashMap✗✗One null keyFast lookup, O(1)
TreeMap✓✗✗Sorted keys, O(log n)

Common Mistake: Modifying a collection while iterating over it. If you add or remove elements during iteration, you'll get a ConcurrentModificationException. Always use an iterator's remove() method, or create a copy before modifying. Also, watch for null values — TreeSet and TreeMap don't accept nulls because they use comparison for ordering.

Best Practices for Collections

  • Use ArrayList for most cases; it's the default choice
  • Use HashSet/HashMap for fast lookups when order doesn't matter
  • Use TreeSet/TreeMap when you need elements sorted
  • Choose the right collection type upfront; switching later is refactoring
  • Use generics (List<String>) to catch type errors at compile time
  • Iterate with for-each loops when you don't need the iterator
  • Use entrySet() not keySet() when iterating over Maps
  • Consider immutable collections (Collections.unmodifiableList) for defensive copying

Real-World Example: E-Commerce Shopping System

A shopping cart uses an ArrayList to maintain items in order; a HashMap maps product IDs to inventory; a TreeMap sorts orders by date; a HashSet tracks unique customer IDs. When displaying the cart, it iterates with a for-each loop. When processing checkout, it validates inventory using the HashMap, then creates an immutable order record. Searching for a product uses binary search on a sorted inventory list. By choosing the right collection for each responsibility, the system is both efficient and maintainable.

Quiz Check

Quick Knowledge Test

Loading quiz questions...