Java Collections Framework — Explained with Examples
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-A1ArrayList vs LinkedList:
| Feature | ArrayList | LinkedList |
|---|---|---|
| Internal structure | Resizable array | Doubly-linked list |
| Get by index | O(1) — instant | O(n) — must traverse |
| Insert/delete at end | O(1) amortized | O(1) |
| Insert/delete in middle | O(n) — shift elements | O(1) — change pointers |
| Memory overhead | Less (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? trueNotice 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 dBmHashMap vs TreeMap:
| Feature | HashMap | TreeMap |
|---|---|---|
| Ordering | None (hash-based) | Sorted (natural or custom comparator) |
| Null keys | One allowed | Not allowed |
| Performance | O(1) for get/put | O(log n) for get/put |
| Use case | Fast lookups | Sorted 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: trueConcurrentHashMap — 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: 2Common Mistakes
Using raw types:
List list = new ArrayList()loses type safety. Always use generics:List<String> list = new ArrayList<>().Modifying a collection while iterating: Using
list.remove()inside a for-each loop throwsConcurrentModificationException. UseIterator.remove()orremoveIf()instead.Using
HashMapwithout thread safety in concurrent code: Multiple threads writing to aHashMapcan cause infinite loops or data corruption. UseConcurrentHashMapinstead.Choosing the wrong
Listimplementation: UsingLinkedListfor indexed access (hundreds ofget(i)calls) results in O(n²) time. UseArrayListfor indexed access.Ignoring
equals()andhashCode(): Custom objects used asHashMapkeys or inHashSetmust implementequals()andhashCode()correctly. Without them, lookups fail.
Practice Questions
- What is the difference between
ArrayListandLinkedList? - When would you use a
TreeMapinstead of aHashMap? - What happens if you add a duplicate element to a
Set? - Why is
ConcurrentHashMappreferred overHashMapin multi-threaded code? - How do you safely remove elements from a collection while iterating?
Answers:
ArrayListuses a dynamic array (O(1) get, O(n) insert in middle).LinkedListuses a doubly-linked list (O(n) get, O(1) insert/delete in middle).- When you need sorted keys (e.g., alphabetical listing, range queries with
subMap()). - The
add()method returnsfalseand the element is ignored. The set remains unchanged. ConcurrentHashMapuses fine-grained locking (lock striping) allowing concurrent reads and limited concurrent writes.HashMapis not thread-safe and can corrupt data.- 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:
- Read a list of devices (name, type, signal strength) from a CSV
- Store them in a
Map<String, Device>keyed by device name - Allow querying by type using a
TreeMap<String, List<Device>>sorted alphabetically - Find devices with signal strength below a threshold
- Output the count of connected vs disconnected devices using
partitioningBy()
This mirrors how DodaTech’s device management API organizes sensor data for dashboard display.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro