Skip to content
Java Collections Framework — Explained with Examples

Java Collections Framework — Explained with Examples

DodaTech Updated Jun 15, 2026 7 min read

The Java Collections Framework is a unified architecture for storing, retrieving, and manipulating groups of objects, providing interfaces and implementations for lists, sets, maps, and queues used in nearly every Java application.

Why the Collections Framework Matters

Without the Collections Framework, every Java developer would need to reinvent data structures. The framework provides battle-tested implementations that handle resizing, sorting, searching, and thread safety. In DodaTech’s backend services, collections are used for everything from caching device lists to grouping analytics data. Understanding which collection to use — and when — directly impacts application performance and memory usage.

Understanding the Core Interfaces

Think of the Collections Framework like a toolbox with specialized tools. You wouldn’t use a sledgehammer to hang a picture frame. Similarly, you shouldn’t use a Vector when an ArrayList works better, or use a HashMap when insertion order matters.

    graph TD
    A[Collection Interface] --> B[List]
    A --> C[Set]
    A --> D[Queue]
    E[Map] -.-> F[Separate root interface]
    B --> G[ArrayList]
    B --> H[LinkedList]
    B --> I[Vector]
    C --> J[HashSet]
    C --> K[TreeSet]
    C --> L[LinkedHashSet]
    D --> M[PriorityQueue]
    D --> N[ArrayDeque]
    E --> O[HashMap]
    E --> P[TreeMap]
    E --> Q[LinkedHashMap]
    E --> R[ConcurrentHashMap]
    style A fill:#3b82f6,color:#fff
    style E fill:#f59e0b,color:#fff
  

List — Ordered, Allows Duplicates

A List is like a numbered shopping list. Items stay in the order you add them, and you can have duplicates. You access items by their index.

import java.util.*;

public class ListDemo {
    public static void main(String[] args) {
        List<String> devices = new ArrayList<>();
        devices.add("Sensor-A1");
        devices.add("Sensor-B2");
        devices.add("Sensor-A1"); // Duplicate allowed
        devices.add(1, "Sensor-C3"); // Insert at index 1

        System.out.println("All devices: " + devices);
        System.out.println("First device: " + devices.get(0));
        System.out.println("Size: " + devices.size());

        // Iterate with for-each
        for (String device : devices) {
            System.out.println("-> " + device);
        }
    }
}

Expected output:

All devices: [Sensor-A1, Sensor-C3, Sensor-B2, Sensor-A1]
First device: Sensor-A1
Size: 4
-> Sensor-A1
-> Sensor-C3
-> Sensor-B2
-> Sensor-A1

ArrayList vs LinkedList:

FeatureArrayListLinkedList
Internal structureResizable arrayDoubly-linked list
Get by indexO(1) — instantO(n) — must traverse
Insert/delete at endO(1) amortizedO(1)
Insert/delete in middleO(n) — shift elementsO(1) — change pointers
Memory overheadLess (contiguous array)More (node objects + pointers)

Use ArrayList when you mostly read and append. Use LinkedList when you frequently insert or delete in the middle.

Set — No Duplicates

A Set is like a bouncer at a club — it only lets unique elements in. Useful for deduplicating data.

import java.util.*;

public class SetDemo {
    public static void main(String[] args) {
        Set<String> uniqueIPs = new HashSet<>();
        uniqueIPs.add("192.168.1.1");
        uniqueIPs.add("192.168.1.2");
        uniqueIPs.add("192.168.1.1"); // Duplicate — ignored
        uniqueIPs.add("192.168.1.3");

        System.out.println("Unique IPs: " + uniqueIPs);
        System.out.println("Count: " + uniqueIPs.size());
        System.out.println("Contains 192.168.1.1? " + uniqueIPs.contains("192.168.1.1"));
    }
}

Expected output:

Unique IPs: [192.168.1.2, 192.168.1.3, 192.168.1.1]
Count: 3
Contains 192.168.1.1? true

Notice HashSet does not guarantee insertion order. Use LinkedHashSet if order matters, or TreeSet for sorted order.

Map — Key-Value Pairs

A Map is like a dictionary — you look up a word (key) to find its definition (value). Maps are the most widely used collection in enterprise Java.

import java.util.*;

public class MapDemo {
    public static void main(String[] args) {
        Map<String, Integer> signalStrengths = new HashMap<>();
        signalStrengths.put("Sensor-A1", -45);
        signalStrengths.put("Sensor-B2", -62);
        signalStrengths.put("Sensor-C3", -38);

        // Look up a value
        System.out.println("Sensor-A1 signal: " + signalStrengths.get("Sensor-A1") + " dBm");

        // Iterate over entries
        for (Map.Entry<String, Integer> entry : signalStrengths.entrySet()) {
            System.out.println(entry.getKey() + " → " + entry.getValue() + " dBm");
        }

        // Check existence
        System.out.println("Has Sensor-D4? " + signalStrengths.containsKey("Sensor-D4"));

        // Default value if missing
        int d4Signal = signalStrengths.getOrDefault("Sensor-D4", -100);
        System.out.println("Sensor-D4 (default): " + d4Signal + " dBm");
    }
}

Expected output:

Sensor-A1 signal: -45 dBm
Sensor-A1 → -45 dBm
Sensor-C3 → -38 dBm
Sensor-B2 → -62 dBm
Has Sensor-D4? false
Sensor-D4 (default): -100 dBm

HashMap vs TreeMap:

FeatureHashMapTreeMap
OrderingNone (hash-based)Sorted (natural or custom comparator)
Null keysOne allowedNot allowed
PerformanceO(1) for get/putO(log n) for get/put
Use caseFast lookupsSorted iteration, range queries

Queue — FIFO Processing

A Queue is like a line at a coffee shop — first in, first out (FIFO). Use it for task scheduling, request buffering, or breadth-first traversal.

import java.util.*;

public class QueueDemo {
    public static void main(String[] args) {
        Queue<String> taskQueue = new LinkedList<>();
        taskQueue.offer("Scan file for malware");
        taskQueue.offer("Compress directory");
        taskQueue.offer("Upload to cloud");
        taskQueue.offer("Send notification");

        System.out.println("Processing queue:");
        while (!taskQueue.isEmpty()) {
            String task = taskQueue.poll();
            System.out.println("  Processing: " + task);
        }
        System.out.println("Queue empty: " + taskQueue.isEmpty());
    }
}

Expected output:

Processing queue:
  Processing: Scan file for malware
  Processing: Compress directory
  Processing: Upload to cloud
  Processing: Send notification
Queue empty: true

ConcurrentHashMap — Thread-Safe Map

In multi-threaded applications, HashMap can corrupt data or throw ConcurrentModificationException. ConcurrentHashMap handles concurrent access without blocking the entire map.

import java.util.concurrent.*;
import java.util.*;

public class ConcurrentMapDemo {
    public static void main(String[] args) throws Exception {
        ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
        scores.put("Thread-A", 0);
        scores.put("Thread-B", 0);

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                scores.compute(Thread.currentThread().getName(),
                    (key, val) -> val + 1);
            }
        };

        Thread t1 = new Thread(task, "Thread-A");
        Thread t2 = new Thread(task, "Thread-B");
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final scores: " + scores);
    }
}

Expected output:

Final scores: {Thread-A=1000, Thread-B=1000}

With a regular HashMap, the counts would almost certainly be less than 1000 due to race conditions.

Using Streams with Collections

The true power of the Collections Framework comes when you pair it with the Java Streams API. Here is a practical example of filtering and transforming device data:

import java.util.*;
import java.util.stream.*;

public class StreamCollectionDemo {
    public static void main(String[] args) {
        List<Device> devices = Arrays.asList(
            new Device("Sensor-A1", "temperature", -45, true),
            new Device("Sensor-B2", "humidity", -62, false),
            new Device("Sensor-C3", "temperature", -38, true),
            new Device("Sensor-D4", "pressure", -55, true),
            new Device("Sensor-E5", "temperature", -70, false)
        );

        List<String> activeTempDevices = devices.stream()
            .filter(d -> d.getType().equals("temperature"))
            .filter(Device::isConnected)
            .sorted(Comparator.comparingInt(Device::getSignalStrength))
            .map(d -> d.getName() + " (" + d.getSignalStrength() + " dBm)")
            .collect(Collectors.toList());

        System.out.println("Active temperature devices (sorted by signal):");
        activeTempDevices.forEach(System.out::println);

        Map<Boolean, List<Device>> partitioned = devices.stream()
            .collect(Collectors.partitioningBy(Device::isConnected));

        System.out.println("\nConnected: " + partitioned.get(true).size());
        System.out.println("Disconnected: " + partitioned.get(false).size());
    }
}

class Device {
    private String name;
    private String type;
    private int signalStrength;
    private boolean connected;

    public Device(String name, String type, int signalStrength, boolean connected) {
        this.name = name;
        this.type = type;
        this.signalStrength = signalStrength;
        this.connected = connected;
    }

    public String getName() { return name; }
    public String getType() { return type; }
    public int getSignalStrength() { return signalStrength; }
    public boolean isConnected() { return connected; }
}

Expected output:

Active temperature devices (sorted by signal):
Sensor-C3 (-38 dBm)
Sensor-A1 (-45 dBm)

Connected: 3
Disconnected: 2

Common Mistakes

  1. Using raw types: List list = new ArrayList() loses type safety. Always use generics: List<String> list = new ArrayList<>().

  2. Modifying a collection while iterating: Using list.remove() inside a for-each loop throws ConcurrentModificationException. Use Iterator.remove() or removeIf() instead.

  3. Using HashMap without thread safety in concurrent code: Multiple threads writing to a HashMap can cause infinite loops or data corruption. Use ConcurrentHashMap instead.

  4. Choosing the wrong List implementation: Using LinkedList for indexed access (hundreds of get(i) calls) results in O(n²) time. Use ArrayList for indexed access.

  5. Ignoring equals() and hashCode(): Custom objects used as HashMap keys or in HashSet must implement equals() and hashCode() correctly. Without them, lookups fail.

Practice Questions

  1. What is the difference between ArrayList and LinkedList?
  2. When would you use a TreeMap instead of a HashMap?
  3. What happens if you add a duplicate element to a Set?
  4. Why is ConcurrentHashMap preferred over HashMap in multi-threaded code?
  5. How do you safely remove elements from a collection while iterating?

Answers:

  1. ArrayList uses a dynamic array (O(1) get, O(n) insert in middle). LinkedList uses a doubly-linked list (O(n) get, O(1) insert/delete in middle).
  2. When you need sorted keys (e.g., alphabetical listing, range queries with subMap()).
  3. The add() method returns false and the element is ignored. The set remains unchanged.
  4. ConcurrentHashMap uses fine-grained locking (lock striping) allowing concurrent reads and limited concurrent writes. HashMap is not thread-safe and can corrupt data.
  5. Use Iterator.remove(), Collection.removeIf(), or collect results to a new collection.

Mini Project: Device Inventory Manager

Build a program that manages a device inventory:

  1. Read a list of devices (name, type, signal strength) from a CSV
  2. Store them in a Map<String, Device> keyed by device name
  3. Allow querying by type using a TreeMap<String, List<Device>> sorted alphabetically
  4. Find devices with signal strength below a threshold
  5. Output the count of connected vs disconnected devices using partitioningBy()

This mirrors how DodaTech’s device management API organizes sensor data for dashboard display.

Related topics: Java, Spring, JVM, GC, API

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro