Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
51: Choosing the Right Collection for a Use Case
I'm working on a small feature for a search application: a "Recent Searches" list. The goal is simple: store the last few things a user searched for, make sure we don't have duplicates, and keep them in the order they were entered. It sounds trivial, but this is exactly where most developers waste time by picking the first collection that comes to mind rather than the one that actually fits the access pattern.
The temptation of the ArrayList
My first instinct is always ArrayList. It's the Swiss Army knife of Java. I'll just throw the search terms in there as they come in. Let's look at how that behaves:
List<String> history = new ArrayList<>();
history.add("Java Collections");
history.add("Spring Boot");
history.add("Java Collections"); // Oops, a duplicate
System.out.println(history); // [Java Collections, Spring Boot, Java Collections]
Right away, we have a problem. The user doesn't want to see "Java Collections" twice in their recent history. To fix this, I could manually check if the list already contains the term before adding it. But wait—ArrayList.contains() is a linear search. If this list grows, I'm iterating through the whole thing every single time the user hits "Enter". That's an $O(n)$ operation for every single insertion. Not ideal.
Trading order for uniqueness
Since I care about uniqueness, the obvious pivot is a HashSet. Sets are designed specifically to prevent duplicates, and they do it in constant time ($O(1)$).
Set<String> history = new HashSet<>();
history.add("Java Collections");
history.add("Spring Boot");
history.add("Java Collections");
System.out.println(history); // [Spring Boot, Java Collections] (maybe)
The duplicates are gone, but now I've hit a new wall: the order is completely unpredictable. A HashSet doesn't remember when I added "Java Collections" versus "Spring Boot". For a "Recent Searches" feature, the order is the entire point. If I can't tell the user what they searched for last, the feature is broken.
Finding the middle ground
This is where I usually stop and look at the hierarchy. I need the uniqueness of a Set, but the ordering of a List. Java has a specific tool for this: the LinkedHashSet. It maintains a doubly-linked list running through its entries, which preserves the insertion order.
Set<String> history = new LinkedHashSet<>();
history.add("Java Collections");
history.add("Spring Boot");
history.add("Microservices");
history.add("Java Collections"); // Duplicate, ignored
System.out.println(history); // [Java Collections, Spring Boot, Microservices]
Now we're getting somewhere. We have uniqueness and we have order. But I noticed one detail: if the user searches for "Java Collections" again, it stays at the top of the list (the oldest position) because it was already there. In a real-world app, a repeat search should probably bump that item to the "most recent" position.
To achieve that with a LinkedHashSet, I have to explicitly remove the item before re-adding it. It's a bit of a workaround, but it's still $O(1)$:
String term = "Java Collections";
history.remove(term);
history.add(term); // Now it's at the end of the insertion order
When a simple set isn't enough
Now, let's say the requirements change. My boss wants to know when each search happened so we can show a timestamp (e.g., "Java Collections - 2 mins ago"). A Set can't do this because it only stores the key.
I could use a HashMap, but as we saw with HashSet, a standard HashMap destroys the order. If I want to map the search term to a timestamp while keeping the search history in order, I'll use a LinkedHashMap.
Map<String, LocalDateTime> history = new LinkedHashMap<>();
history.put("Java Collections", LocalDateTime.now());
history.put("Spring Boot", LocalDateTime.now());
// The LinkedHashMap remembers the order of the keys
// and gives me the value (timestamp) for each.
When you're choosing your collection, don't just ask "Does this hold my data?" Ask:
- Do I need to prevent duplicates? (Set)
- Does the order of arrival matter? (Linked... or List)
- Do I need to associate this data with another piece of data? (Map)
- How often will I be searching for a specific item? (Set/Map vs List)
📋 Practical Task
Implement a Fixed-Size LRU (Least Recently Used) Cache
In this exercise, you will build a simplified "Recently Viewed Products" cache for an e-commerce site. The cache should store a maximum of 3 product IDs. If a 4th product is added, the oldest one (the one least recently accessed) should be removed.
Requirements:
- Use a
LinkedHashMapto store the product IDs. - The cache must maintain insertion order.
- Implement a method
viewProduct(String productId)that:- Adds the product to the cache.
- If the product was already in the cache, it should be moved to the "most recent" position.
- If the cache size exceeds 3, it must remove the oldest entry.
Starter Code:
import java.util.*;
public class ProductCache {
private final int MAX_CAPACITY = 3;
private LinkedHashMap<String, Boolean> cache = new LinkedHashMap<>();
public void viewProduct(String productId) {
// Your logic here:
// 1. Handle potential duplicate to move it to the end
// 2. Add the product
// 3. Check size and remove the first (oldest) entry if necessary
}
public void printCache() {
System.out.println("Recently Viewed: " + cache.keySet());
}
public static void main(String[] args) {
ProductCache myCache = new ProductCache();
myCache.viewProduct("Laptop");
myCache.viewProduct("Mouse");
myCache.viewProduct("Keyboard");
myCache.printCache(); // Expected: [Laptop, Mouse, Keyboard]
myCache.viewProduct("Laptop"); // Should move Laptop to the end
myCache.printCache(); // Expected: [Mouse, Keyboard, Laptop]
myCache.viewProduct("Monitor"); // Should remove Mouse
myCache.printCache(); // Expected: [Keyboard, Laptop, Monitor]
}
}There are no comments for now.