Java Collection Framework: A Practical Guide with Examples

JAVA DEVELOPMENT · COLLECTIONS AND DATA STRUCTURES

Java Collection Framework: A Practical Guide with Examples

Understand List, Set, Queue, and Map—and choose the right collection for reliable, efficient Java applications.

Choosing the wrong collection can turn a simple lookup into repeated scanning, remove information you needed, or produce results in an unexpected order.

The Java Collection Framework, officially called the Java Collections Framework, provides standard interfaces and implementations for managing groups of objects. Understanding it means knowing more than how to call add() and remove().

This guide explains the main collection types, their performance characteristics, and how to choose between them. You will also learn practical techniques for removing duplicates, counting values, protecting collection contents, and handling shared data safely.

1. What is the Java Collection Framework?

The framework provides three main building blocks:

  • Interfaces, such as List, Set, and Map, define behavior.
  • Implementations, such as ArrayList, HashSet, and HashMap, provide concrete data structures.
  • Algorithms and utilities support operations such as sorting, searching, and wrapping collections.

Most core types belong to java.util. Specialized concurrent collections belong to java.util.concurrent.

Unlike arrays, which have a fixed length after creation, many collection implementations grow as elements are added. However, not every collection supports modification. Some are fixed-size or unmodifiable.

Collection vs. Collections vs. Map

Name Meaning Example
Collection Root interface for groups of individual elements Collection<String>
Collections Utility class with static helper methods Collections.sort(names)
Map Interface for key-value associations Map<String, Integer>

Remember: Map belongs to the framework but does not extend Collection. It exposes collection views through keySet(), values(), and entrySet().

2. Core interfaces in the Java Collection Framework

List: Preserve position and allow duplicates

A List stores elements in a defined sequence and provides index-based access. Common implementations allow duplicate values.

Use a list when position matters, such as displaying search results or retaining imported records in their original order.

List<String> stores = new ArrayList<>(); // Create a resizable list.
stores.add("Delhi");                     // Add the first store.
stores.add("Mumbai");                    // Add the second store.
stores.add("Delhi");                     // Keep the duplicate.
System.out.println(stores.get(0));       // Print Delhi.

ArrayList vs. LinkedList

ArrayList uses a resizable backing array. It provides constant-time indexed access and amortized constant-time appends. Inserting near the beginning usually requires shifting existing elements.

LinkedList requires traversal to reach an indexed position. Therefore, “linked lists are faster for insertions” is incomplete advice: locating the insertion point can dominate the operation.

For ordinary lists that are frequently read and appended to, start with ArrayList. Choose another implementation when your access pattern justifies it.

Set: Maintain unique elements

A Set represents distinct elements. The implementation determines ordering:

Implementation Ordering behavior Typical purpose
HashSet No guaranteed iteration order Membership checks
LinkedHashSet Insertion order Deduplication while retaining order
TreeSet Sorted order Sorted unique values and range queries

For example, preserve the first occurrence of each store:

List<String> stores = Arrays.asList("Delhi", "Mumbai", "Delhi"); // Input.
Set<String> unique = new LinkedHashSet<>(stores); // Remove duplicates.
System.out.println(unique);                     // [Delhi, Mumbai]

A TreeSet uses natural ordering or a supplied comparator. Its basic operations take O(log n) time. Be careful: elements that compare as zero are treated as duplicates, even if their equals() results differ.

Queue and Deque: Process pending work

A Queue holds elements awaiting processing. Many queues use first-in, first-out ordering, but this is not universal: PriorityQueue selects elements according to priority.

A Deque supports insertion and removal at both ends. ArrayDeque is useful for local queues and stacks.

Deque<String> files = new ArrayDeque<>(); // Create a queue.
files.offerLast("cashsheet-001.csv");     // Add the first file.
files.offerLast("cashsheet-002.csv");     // Add the second file.
System.out.println(files.pollFirst());   // Process the first file.

pollFirst() returns null when empty. removeFirst() throws an exception instead. Select the method that matches how your application handles an empty queue.

Map: Retrieve values by key

A Map associates each unique key with a value. Values may repeat.

  • HashMap: Lookup without an ordering requirement.
  • LinkedHashMap: Predictable insertion-order iteration by default.
  • TreeMap: Sorted keys.
Map<String, Integer> totals = new HashMap<>(); // Store counts by store.
totals.put("STORE-101", 12);                   // Add a mapping.
totals.put("STORE-101", 15);                   // Replace its value.
System.out.println(totals.get("STORE-101"));  // Print 15.

HashMap provides expected constant-time get() and put() operations when hashes are distributed appropriately. It does not guarantee iteration order.

3. How to choose the right collection

Start with the operation your application performs most often.

Requirement Starting choice Reason
Access elements by index ArrayList Constant-time indexed access
Check whether a value exists HashSet Expected constant-time membership checks
Remove duplicates while preserving order LinkedHashSet Uniqueness with insertion order
Retrieve a value using an identifier HashMap Efficient key-based lookup
Maintain sorted unique values TreeSet Ordering and navigation
Add and remove at both ends ArrayDeque Efficient endpoint operations
Share key-value data across threads ConcurrentHashMap Concurrent access support

Performance also depends on memory, data size, hashing quality, and the mix of operations.

For example, repeatedly calling contains() on a large ArrayList scans its elements each time. Building a HashSet first can improve repeated membership checks, at the cost of extra storage.

4. Practical example: Count files by restaurant

Suppose a processing service receives restaurant identifiers from uploaded cash-sheet records. You need the number of records for each restaurant.

This Java 8-compatible example uses the Stream API:

import java.util.*;                  // Import collection types.
import java.util.stream.Collectors; // Import grouping utilities.

public class RestaurantCounts { // Define the executable class.
    public static void main(String[] args) { // Start execution.
        List<String> stores = Arrays.asList(
                "R101", "R102", "R101", "R103", "R102"); // Input records.

        Map<String, Long> counts = stores.stream() // Read each identifier.
                .collect(Collectors.groupingBy(
                        store -> store,          // Group by identifier.
                        LinkedHashMap::new,      // Preserve first-seen order.
                        Collectors.counting())); // Count each group.

        System.out.println(counts); // Display the grouped counts.
    }
}

Expected output

{R101=2, R102=2, R103=1}

The flow is straightforward:

  1. Read each restaurant identifier.
  2. Find or create its group.
  3. Increase the group’s count.
  4. Return a map in first-seen order.

Under normal hashing assumptions, processing takes expected O(n) time and O(k) extra space, where k is the number of distinct restaurants.

The collection stores data; the stream describes how to process it. They serve different purposes.

5. Common mistakes and how to avoid them

Incorrect equality for custom objects

Hash-based collections depend on both equals() and hashCode().

If two objects are logically equal, they must return the same hash code. Different objects may still share a hash code; equality checks distinguish them.

Avoid changing fields used by these methods while an object is stored as a map key or set element. Otherwise, lookup may fail because its hash no longer matches its original storage location.

Confusing unmodifiable with deeply immutable

List.of(), introduced in Java 9, creates an unmodifiable list. Java 10 added List.copyOf().

List<String> markets = List.of("IN", "UK"); // Create an unmodifiable list.

These factories reject null elements. Adding, removing, or replacing elements is unsupported.

However, they do not freeze mutable objects stored inside the list. Protecting the list structure and protecting each element are separate concerns.

Removing elements during iteration

Directly removing elements from an ArrayList inside an enhanced for loop can cause ConcurrentModificationException.

For straightforward filtering, use:

stores.removeIf(String::isEmpty); // Remove empty strings from a mutable list.

This requires a list that supports removal. The fixed-size list returned by Arrays.asList() does not support structural changes.

Assuming thread safety covers every workflow

ArrayList and HashMap are not synchronized.

For shared counters, a ConcurrentHashMap can perform a per-key update atomically:

import java.util.concurrent.ConcurrentHashMap; // Import the concurrent map.

ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>(); // Shared map.
counts.merge("R101", 1, Integer::sum); // Atomically initialize or increment.

A separate get() followed by put() is not an atomic increment, even on a concurrent map. Also, thread-safe map operations do not automatically protect mutable objects stored as values.

6. Put collection selection into practice

Choose a collection by answering four questions: Do duplicates matter? Must order be preserved? How will elements be accessed? Will multiple threads modify the data?

Start with ArrayList, HashSet, or HashMap for common requirements. Move to ordered, sorted, or concurrent alternatives when needed. Then verify behavior with representative data, especially equality, ordering, modification support, and performance.

Official documentation

Comments

Popular posts from this blog

Beyond Standard RAG: The Architecture of Agentic Retrieval and Self-Correction Loops

The Architecture of Vision-Language Models: How Multimodal AI Sees and Understands the World