A cache is a data structure used to store frequently accessed data temporarily, improving performance by avoiding repeated expensive computations or data retrievals. The core idea is to trade memory usage for faster access times.
The Map
interface in Java is naturally suited for caching:
get
, put
, and remove
offer fast retrieval and insertion (especially with HashMap
or ConcurrentHashMap
).At its simplest, a cache can be a Map<K, V>
where keys are used to store and retrieve values. However, real-world caches often need features like:
While Java doesn’t provide a built-in LRU cache directly in the standard Map
, it allows creating one using LinkedHashMap
by overriding removeEldestEntry
.
import java.util.LinkedHashMap;
import java.util.Map;
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true); // 'true' for access-order
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
public class CacheDemo {
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "One");
cache.put(2, "Two");
cache.put(3, "Three");
System.out.println("Cache before access: " + cache);
cache.get(1); // Access key 1, making it most recently used
cache.put(4, "Four"); // Evicts key 2 (least recently used)
System.out.println("Cache after eviction: " + cache);
}
}
Output:
Cache before access: {1=One, 2=Two, 3=Three}
Cache after eviction: {3=Three, 1=One, 4=Four}
LinkedHashMap
with access-order set to true
, so entries are reordered every time they are accessed.removeEldestEntry()
triggers eviction when the size exceeds the predefined capacity.get()
and put()
are O(1) on average, making it suitable for high-performance use cases.For more advanced scenarios (e.g., thread-safe or time-based expiration), third-party libraries like Caffeine or Guava are recommended, but for many cases, this LinkedHashMap-based approach is more than sufficient.
Caching is a vital performance optimization technique in software systems, and the Java Map
interface provides an excellent foundation for implementing caches. By leveraging LinkedHashMap
, developers can build lightweight LRU caches with minimal code. Understanding how to apply and customize this pattern is a valuable skill when working with collections in real-world applications.
Collections play a central role in building data processing pipelines—a sequence of operations applied to data to transform, filter, group, or aggregate it. Whether processing user records, log entries, or financial transactions, Java collections like List
, Map
, and Queue
help structure each step of the pipeline.
These pipelines can be implemented using iterative loops or more modern functional approaches via the Streams API. Collections serve as the source, intermediate, and target containers throughout the pipeline.
List
or Queue
to store incoming data.Map
to group elements by a key.Let’s simulate a data pipeline that filters active users, groups them by role, and outputs the result.
import java.util.*;
import java.util.stream.Collectors;
class User {
String name;
String role;
boolean isActive;
User(String name, String role, boolean isActive) {
this.name = name;
this.role = role;
this.isActive = isActive;
}
@Override
public String toString() {
return name;
}
}
public class UserPipeline {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Alice", "Admin", true),
new User("Bob", "User", false),
new User("Charlie", "User", true),
new User("Diana", "Admin", true),
new User("Eve", "Guest", false)
);
// Step 1: Filter active users
List<User> activeUsers = users.stream()
.filter(user -> user.isActive)
.collect(Collectors.toList());
// Step 2: Group by role
Map<String, List<User>> groupedByRole = activeUsers.stream()
.collect(Collectors.groupingBy(user -> user.role));
// Output the grouped result
groupedByRole.forEach((role, group) -> {
System.out.println(role + ": " + group);
});
}
}
Expected Output:
Admin: [Alice, Diana]
User: [Charlie]
Map<String, List<User>>
.This pipeline leverages the Streams API for readability and efficiency, but similar logic could be implemented using loops and conditionals, especially in older Java versions.
Queue
(e.g., ArrayDeque
) for rate-limited processing.TreeMap
or LinkedHashMap
to preserve order.Map
values with List
records in memory.Collections are essential for organizing data flow in processing pipelines. They enable each stage—from ingestion to output—to operate on well-structured data. Whether using traditional loops or declarative streams, understanding how to combine and transform collections effectively is key to writing clean, performant data-driven code.
Collections such as List
, Set
, and Map
form the backbone of modeling graph and tree data structures in Java. These structures are essential in domains like navigation systems, compilers, social networks, and hierarchical data modeling (e.g., organization charts).
A graph is a set of nodes (vertices) connected by edges. It can be directed or undirected, and may include cycles.
The most common and efficient way to represent a graph in Java is via an adjacency list, typically implemented as:
Map<String, List<String>> adjacencyList = new HashMap<>();
Each key is a node, and the corresponding value is a list of its adjacent nodes.
import java.util.*;
public class GraphExample {
public static void main(String[] args) {
Map<String, List<String>> graph = new HashMap<>();
// Adding nodes and edges
graph.put("A", Arrays.asList("B", "C"));
graph.put("B", Arrays.asList("D"));
graph.put("C", Arrays.asList("D"));
graph.put("D", Collections.emptyList());
// Print the graph
graph.forEach((node, edges) -> {
System.out.println(node + " -> " + edges);
});
}
}
Output:
A -> [B, C]
B -> [D]
C -> [D]
D -> []
Traversal can be done using Breadth-First Search (BFS) or Depth-First Search (DFS), implemented with Queue
or recursion respectively.
A tree is a hierarchical structure with a root node and child nodes, where each child has exactly one parent. Common representations include:
import java.util.*;
class TreeNode {
String name;
List<TreeNode> children;
TreeNode(String name) {
this.name = name;
this.children = new ArrayList<>();
}
void addChild(TreeNode child) {
children.add(child);
}
}
public class TreeExample {
public static void main(String[] args) {
TreeNode root = new TreeNode("Root");
TreeNode a = new TreeNode("A");
TreeNode b = new TreeNode("B");
TreeNode c = new TreeNode("C");
root.addChild(a);
root.addChild(b);
a.addChild(c);
printTree(root, 0);
}
// Recursive DFS traversal
static void printTree(TreeNode node, int depth) {
System.out.println(" ".repeat(depth) + node.name);
for (TreeNode child : node.children) {
printTree(child, depth + 1);
}
}
}
Output:
Root
A
C
B
Graph:
Tree:
Java’s collection classes provide powerful tools for modeling complex structures like graphs and trees. By combining Map
, List
, and Set
in intuitive ways, developers can efficiently represent and manipulate hierarchical and relational data. Whether modeling family trees, dependency graphs, or organizational hierarchies, understanding how to map these structures to collections is a vital programming skill.