Java tutorials  /  Java Collections Framework
Chapter 15 · Java

Java Collections Framework

The Java Collections Framework is a unified architecture of interfaces and classes for storing, manipulating, and processing groups of objects as a single unit. It provides ready-to-use, dynamically resizable data structures like lists, sets, and maps, along with algorithms for sorting, searching, and iterating over them, all built around core interfaces such as Collection, List, Set, and Map.

Think of the Collections Framework as a well-organized toolbox of different container types, each suited to a different storage need. A 'List' is like a numbered shelf where order matters and duplicates are allowed — you can have the same book twice on different shelf spots. A 'Set' is like a stamp collection where every stamp must be unique — adding a duplicate is simply ignored. A 'Map' is like a filing cabinet with labeled folders, where you look something up by its label (key) rather than its position, and each label can only point to one folder.

Consider a social media platform. The list of a user's most recent notifications, where order and duplicates matter (you could get the same 'liked your post' notification twice) and you need to display them in exact chronological sequence, would use a 'List' like ArrayList. The set of unique user IDs who have blocked a particular account, where each ID must appear only once and quick 'is this user blocked?' checks are essential, would use a 'Set' like HashSet. A user's profile settings, where each setting name (like 'theme' or 'language') maps to a specific value, and you need to look up a setting's value directly by its name rather than searching through a list, would use a 'Map' like HashMap — this exact pattern of List/Set/Map usage appears constantly across virtually every real-world Java application's data layer.

Before the Collections Framework, Java developers had to rely on basic, fixed-size arrays or write custom, error-prone data structure implementations from scratch for common needs like dynamic lists or key-value lookups. The Collections Framework solves this by providing a standardized, well-tested, and highly optimized set of reusable data structures and algorithms, dramatically reducing development time, minimizing bugs, ensuring consistent behavior across different parts of a codebase, and offering built-in support for growing/shrinking collections dynamically, which raw arrays cannot do.

  • List Interface (Ordered, Allows Duplicates): Represents an ordered collection where elements can be accessed by their integer index, and duplicate elements are permitted. Key implementations include 'ArrayList' (fast random access, backed by a dynamic array) and 'LinkedList' (fast insertion/deletion, backed by a doubly-linked list).
  • Set Interface (Unique Elements): Represents a collection that does not allow duplicate elements. Key implementations include 'HashSet' (no guaranteed order, fastest performance), 'LinkedHashSet' (maintains insertion order), and 'TreeSet' (maintains elements in sorted order).
  • Map Interface (Key-Value Pairs): Represents a collection of key-value pairs where each key must be unique, but values can be duplicated. Key implementations include 'HashMap' (no guaranteed order, fastest performance), 'LinkedHashMap' (maintains insertion order), and 'TreeMap' (maintains keys in sorted order). Note: Map does not extend the Collection interface directly.
  • Queue Interface (FIFO/Priority-Based Processing): Represents a collection designed for holding elements prior to processing, typically in First-In-First-Out (FIFO) order. Key implementations include 'LinkedList' (as a Queue) and 'PriorityQueue' (elements are processed based on their natural ordering or a custom comparator, not insertion order).
// List: List<Type> list = new ArrayList<>(); list.add(element); // Set: Set<Type> set = new HashSet<>(); set.add(element); // Map: Map<KeyType, ValueType> map = new HashMap<>(); map.put(key, value); ValueType value = map.get(key);
A beginner needs to manage a small online store's data: a list of items in a customer's shopping cart (allowing duplicate products), a set of unique product categories available in the store, and a map linking each product's ID to its price for quick lookups.
Using ArrayList for an Ordered Shopping Cart
This example demonstrates creating an ArrayList to store a shopping cart's items, allowing duplicate entries and maintaining insertion order, then iterating through it.
Java
import java.util.ArrayList; import java.util.List; public class ShoppingCartDemo { public static void main(String[] args) { List<String> cart = new ArrayList<>(); cart.add("Laptop"); cart.add("Mouse"); cart.add("Laptop"); // Duplicate allowed System.out.println("Cart Contents: " + cart); System.out.println("Total Items: " + cart.size()); } }
Cart Contents: [Laptop, Mouse, Laptop] Total Items: 3
'List<String> cart = new ArrayList<>();' declares the reference variable using the 'List' interface type (a best practice), while instantiating the actual object as an 'ArrayList'. The '.add()' method appends elements in order, and since List permits duplicates, "Laptop" appears twice in the resulting cart. 'cart.size()' returns the total number of elements currently stored, which correctly reflects all three additions including the duplicate.
Using HashMap for Fast Key-Based Lookups
This example uses a HashMap to associate product names (keys) with their prices (values), demonstrating fast direct lookups without needing to search through a list sequentially.
Java
import java.util.HashMap; import java.util.Map; public class ProductPriceDemo { public static void main(String[] args) { Map<String, Double> productPrices = new HashMap<>(); productPrices.put("Laptop", 899.99); productPrices.put("Mouse", 19.99); productPrices.put("Keyboard", 49.99); double laptopPrice = productPrices.get("Laptop"); System.out.println("Laptop Price: $" + laptopPrice); if (productPrices.containsKey("Monitor")) { System.out.println("Monitor price found."); } else { System.out.println("Monitor not found in price list."); } } }
Laptop Price: $899.99 Monitor not found in price list.
'productPrices.put("Laptop", 899.99);' associates the key "Laptop" directly with the value 899.99 internally using a hashing mechanism, allowing 'productPrices.get("Laptop")' to retrieve this value almost instantly, without needing to check every single entry sequentially like a List would require. 'containsKey()' safely checks whether a specific key exists in the map before attempting to retrieve it, preventing a null value from being mistakenly treated as a valid price if the key were absent.
  • Using a List When Uniqueness Should Be Enforced: Using an 'ArrayList' to store items that must be unique (like unique user IDs or product categories) requires manually checking 'if (!list.contains(item))' before every single addition to prevent duplicates, which is both inefficient (an O(n) search each time) and error-prone if a developer forgets this check somewhere. A 'HashSet' automatically and efficiently enforces uniqueness without requiring this manual check at all.
  • Assuming HashMap or HashSet Maintains Insertion Order: Beginners often expect that iterating over a 'HashMap' or 'HashSet' will yield elements in the same order they were added, but this is NOT guaranteed at all — their internal hashing-based structure can produce any order, which may even change between different runs of the same program. If insertion order must be preserved, 'LinkedHashMap' or 'LinkedHashSet' should be used instead.
  • Calling get() on a Map with a Non-Existent Key Without Checking First: Calling 'map.get(someKey)' when 'someKey' doesn't actually exist in the map returns 'null' rather than throwing an exception. If this null result is then used directly without a null-check (e.g., immediately calling a method on it, or unboxing it into a primitive type like 'int'), it can lead to a 'NullPointerException' later in the code, often far from where the actual missing-key issue originated.
  • Modifying a Collection While Iterating Over It Directly: Directly calling 'list.remove(item)' or 'map.remove(key)' on the collection itself while inside an enhanced for-each loop iterating over that same collection throws a 'ConcurrentModificationException' at runtime. The correct approach is to use the collection's own 'Iterator' object and its 'remove()' method, or to collect items to be removed in a separate temporary list first and remove them afterward, outside the main iteration loop.
  • Choose the Right Collection Type Based on Actual Requirements: Select List when order matters and duplicates are acceptable, Set when uniqueness must be strictly enforced and order is generally unimportant (unless using LinkedHashSet/TreeSet), and Map when data needs to be looked up efficiently by a unique key rather than searched through sequentially. Choosing the correct collection type upfront avoids inefficient workarounds later.
  • Program to the Interface, Not the Implementation: Always declare collection variables using their interface type (e.g., 'List<String> list = new ArrayList<>();' rather than 'ArrayList<String> list = new ArrayList<>();'), allowing the underlying concrete implementation to be swapped later (e.g., to a LinkedList) with minimal changes required elsewhere in the code that uses this variable.
  • Use getOrDefault() or computeIfAbsent() for Safer Map Access: Instead of manually checking 'containsKey()' followed by a separate 'get()' call, use 'map.getOrDefault(key, defaultValue)' to safely retrieve a value with a built-in fallback, or 'map.computeIfAbsent(key, k -> defaultValueSupplier)' when a missing key should trigger the creation and insertion of a new default value, both reducing boilerplate and the risk of null-related bugs.
  • Use Diamond Operator (<>) for Cleaner Generic Instantiation: When declaring and instantiating a generic collection, use the diamond operator to let the compiler infer the generic type from the left-hand side declaration (e.g., 'Map<String, Integer> scores = new HashMap<>();' instead of repeating the full generic type on the right side), keeping code more concise and reducing redundancy.
What is the difference between ArrayList and LinkedList in Java, and when would you choose one over the other?
'ArrayList' is backed internally by a dynamically resizable array, offering fast, constant-time O(1) random access to elements by index (e.g., 'list.get(5)'), but insertion or deletion in the middle of the list is relatively slow (O(n)) since subsequent elements must be shifted to fill or create the gap. 'LinkedList' is backed by a doubly-linked list structure, offering fast, constant-time O(1) insertion and deletion at the beginning or middle of the list (once you have a reference to that position) since it just involves updating adjacent node pointers, but random access by index is slow (O(n)) since it must traverse the list sequentially from one end to reach a specific position. Choose ArrayList when frequent random access/reads are the primary operation; choose LinkedList when frequent insertions/deletions (especially at the beginning or middle of the list) are the primary operation.
Why doesn't the Map interface extend the Collection interface in Java's Collections Framework?
The 'Collection' interface is fundamentally designed around holding a group of individual, single elements (like 'add(element)' and iterating over single items). 'Map', however, represents a fundamentally different data structure: a collection of KEY-VALUE PAIRS, where operations naturally center around keys mapping to values (like 'put(key, value)' and 'get(key)'), which doesn't fit cleanly into the single-element-based method contracts defined by the Collection interface. Because of this fundamental structural mismatch, Java's designers made Map a separate, independent interface hierarchy, though Map does provide methods like 'keySet()', 'values()', and 'entrySet()' that return actual Collection-based views (a Set of keys, a Collection of values, and a Set of key-value Entry objects respectively) for convenient iteration when needed.
What is the difference between HashSet, LinkedHashSet, and TreeSet in Java?
'HashSet' offers the fastest average performance (O(1) for add/remove/contains operations) by using a hash table internally, but provides absolutely no guarantee about the iteration order of its elements. 'LinkedHashSet' extends HashSet's hashing mechanism but additionally maintains a doubly-linked list running through all its entries, preserving the exact INSERTION order during iteration, at a slight performance cost compared to plain HashSet. 'TreeSet' stores elements in a sorted, natural order (or according to a custom Comparator provided at creation) by using a self-balancing tree structure internally, offering O(log n) performance for add/remove/contains — slower than HashSet, but uniquely providing guaranteed sorted-order iteration and additional range-based query methods (like 'headSet()' and 'tailSet()') that the other two implementations don't offer at all.
Write a Java program using a HashSet to store a list of product categories, attempting to add a duplicate category, and print the final set along with its size.
import java.util.HashSet; import java.util.Set; public class CategorySetDemo { public static void main(String[] args) { Set<String> categories = new HashSet<>(); categories.add("Electronics"); categories.add("Clothing"); categories.add("Electronics"); // Duplicate, will be ignored System.out.println("Categories: " + categories); System.out.println("Total unique categories: " + categories.size()); } } // Output (order may vary since HashSet does not guarantee order): // Categories: [Electronics, Clothing] // Total unique categories: 2
What will be the output of the following code, and why? import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) { Map<String, Integer> scores = new HashMap<>(); scores.put("Alice", 90); Integer bobScore = scores.get("Bob"); System.out.println("Bob's score: " + bobScore); } }
The output will be: 'Bob's score: null'. Since the key "Bob" was never added to the 'scores' map, calling 'scores.get("Bob")' returns 'null' rather than throwing an exception, since 'get()' on a Map simply returns null for any key that isn't present. When this null value is then concatenated into the print statement using the '+' operator, Java's automatic String conversion converts null into the literal text "null" for display purposes. Note: if 'bobScore' were declared as a primitive 'int' instead of the wrapper class 'Integer', attempting to unbox this null value would instead throw a 'NullPointerException' at runtime.
Write a Java program using a TreeMap to store three student names as keys and their exam scores as values, then print the map, demonstrating that TreeMap automatically sorts entries by key.
import java.util.Map; import java.util.TreeMap; public class TreeMapDemo { public static void main(String[] args) { Map<String, Integer> studentScores = new TreeMap<>(); studentScores.put("Charlie", 78); studentScores.put("Alice", 92); studentScores.put("Bob", 85); System.out.println("Sorted Student Scores: " + studentScores); } } // Output: // Sorted Student Scores: {Alice=92, Bob=85, Charlie=78} // Even though entries were added in the order Charlie, Alice, Bob, TreeMap automatically maintains its keys in natural sorted (alphabetical, for Strings) order internally, which is reflected when the map is printed or iterated.

The Java Collections Framework provides a standardized set of interfaces — List (ordered, allows duplicates), Set (unique elements), and Map (key-value pairs) — along with efficient implementations like ArrayList, HashSet, and HashMap, eliminating the need to manually build common dynamic data structures from scratch. Choosing the right collection type based on whether order, duplicates, or key-based lookups matter, understanding the performance trade-offs between implementations like ArrayList versus LinkedList, and being aware of pitfalls like ConcurrentModificationException and unordered iteration in HashMap/HashSet are essential skills for writing efficient, correct real-world Java applications.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.