Introduction
The HashMap class in Java is a fundamental implementation of the Map interface that stores data as key-value pairs, where each key is unique. It provides constant-time performance for basic operations like get() and put() under ideal conditions, making it one of the most efficient and widely used data structures in Java. This guide covers essential HashMap operations—including insertion, retrieval, removal, iteration, and advanced features—along with best practices, performance considerations, and common pitfalls.
1. Core HashMap Operations
A. Adding/Updating Entries
| Method | Description | Example |
|---|---|---|
put(K key, V value) | Inserts or updates a key-value pair | map.put("Alice", 95); |
putIfAbsent(K key, V value) (Java 8+) | Inserts only if key is not present | map.putIfAbsent("Bob", 80); |
Note: If the key already exists,
put()replaces the old value.
B. Retrieving Values
| Method | Description | Example |
|---|---|---|
get(Object key) | Returns value for key (null if not found or value is null) | Integer score = map.get("Alice"); |
getOrDefault(Object key, V defaultValue) (Java 8+) | Returns value or default if key missing | int s = map.getOrDefault("Eve", 0); |
Caution:
get()returnsnullfor both missing keys and actualnullvalues.
C. Removing Entries
| Method | Description | Example |
|---|---|---|
remove(Object key) | Removes and returns value for key | map.remove("Alice"); |
remove(Object key, Object value) (Java 8+) | Removes only if key maps to specified value | map.remove("Alice", 95); |
D. Checking Existence
| Method | Description | Example |
|---|---|---|
containsKey(Object key) | Checks if key exists | boolean has = map.containsKey("Alice"); |
containsValue(Object value) | Checks if value exists (O(n) time) | boolean hasVal = map.containsValue(95); |
isEmpty() | Checks if map has no entries | if (map.isEmpty()) { ... } |
size() | Returns number of key-value pairs | int n = map.size(); |
2. Bulk Operations
A. Adding Multiple Entries
Map<String, Integer> other = Map.of("Charlie", 88, "David", 92);
map.putAll(other); // Adds all entries from 'other'
B. Replacing Values
// Replace value only if key exists
map.replace("Alice", 98);
// Replace only if current value matches
map.replace("Alice", 95, 100);
C. Computing Values (Java 8+)
// Compute if absent
map.computeIfAbsent("Eve", k -> 0);
// Compute if present
map.computeIfPresent("Alice", (k, v) -> v + 5);
// Compute regardless
map.compute("Bob", (k, v) -> (v == null) ? 0 : v + 10);
D. Merging Entries (Java 8+)
// Merge "Alice" with new value using a remapping function
map.merge("Alice", 5, Integer::sum); // Adds 5 to existing value
3. Iteration Techniques
A. Iterating Over Keys
for (String key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}
B. Iterating Over Values
for (Integer value : map.values()) {
System.out.println(value);
}
C. Iterating Over Entries (Most Efficient)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
D. Using forEach() (Java 8+)
map.forEach((key, value) -> System.out.println(key + " -> " + value));
Best Practice: Use entrySet() for key-value access—it avoids repeated
get()calls.
4. Advanced Operations
A. Filtering and Searching
// Find all entries with score > 90 map.entrySet().stream() .filter(e -> e.getValue() > 90) .forEach(System.out::println);
B. Transforming Values
// Increment all scores by 5 map.replaceAll((k, v) -> v + 5);
C. Grouping Data (Common Use Case)
// Group students by grade Map<String, List<String>> gradeMap = students.stream() .collect(Collectors.groupingBy(Student::getGrade));
5. Performance Characteristics
| Operation | Average Time Complexity | Worst Case (Java 8+) |
|---|---|---|
get(key) | O(1) | O(log n) (after tree conversion) |
put(key, value) | O(1) | O(log n) |
remove(key) | O(1) | O(log n) |
containsKey(key) | O(1) | O(log n) |
containsValue(value) | O(n) | O(n) |
| Iteration | O(n) | O(n) |
Note: Java 8+ converts buckets with >8 entries from linked lists to balanced trees, improving worst-case performance.
6. Best Practices
- Use immutable objects as keys (e.g.,
String,Integer) to preventhashCode()changes. - Override
hashCode()andequals()properly in custom key classes:
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
- Specify initial capacity to avoid rehashing:
Map<String, Integer> map = new HashMap<>(100); // For ~75 entries (0.75 load factor)
- Prefer
ConcurrentHashMapfor thread-safe scenarios. - Use
getOrDefault()instead of null checks:
// Instead of:
Integer val = map.get("key");
if (val == null) val = 0;
// Use:
int val = map.getOrDefault("key", 0);
7. Common Pitfalls
- Modifying keys after insertion: Changing a key’s state alters its
hashCode(), making it unfindable.
MutableKey key = new MutableKey("A");
map.put(key, 100);
key.setName("B"); // Now map.get(key) returns null!
- Using
nullkeys carelessly: Only onenullkey is allowed;get(null)may be ambiguous. - Ignoring thread safety:
HashMapis not synchronized—useConcurrentHashMapin multi-threaded code. - Assuming order:
HashMapdoes not maintain insertion order (useLinkedHashMapif needed).
8. HashMap vs. Other Map Implementations
| Feature | HashMap | LinkedHashMap | TreeMap | ConcurrentHashMap |
|---|---|---|---|---|
| Order | No order | Insertion/access order | Sorted (natural/custom) | No order |
| Null Keys | One | One | Not allowed | One (but discouraged) |
| Thread-Safe | No | No | No | ✅ Yes |
| Time Complexity | O(1) avg | O(1) avg | O(log n) | O(1) avg |
| Use Case | General-purpose | Predictable iteration | Sorted keys | Concurrent access |
9. Practical Examples
A. Word Frequency Counter
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
wordCount.merge(word, 1, Integer::sum);
}
B. Caching Computed Results
Map<String, BigDecimal> cache = new HashMap<>();
public BigDecimal computeExpensiveValue(String key) {
return cache.computeIfAbsent(key, k -> performExpensiveCalculation(k));
}
C. Configuration Settings
Map<String, String> config = new HashMap<>();
config.put("timeout", "30s");
config.put("retries", "3");
String timeout = config.getOrDefault("timeout", "60s");
Conclusion
HashMap is a cornerstone of efficient data management in Java, offering fast key-based access and a rich set of operations for modern development. By leveraging its core methods, Java 8+ enhancements like computeIfAbsent() and merge(), and following best practices—such as proper key design and capacity planning—developers can build high-performance, scalable applications. However, it’s crucial to understand its limitations regarding ordering, thread safety, and key mutability. When used appropriately, HashMap provides an optimal balance of speed, simplicity, and flexibility for a vast array of programming tasks—from caching and indexing to data aggregation and configuration management. Always choose the right map implementation for your specific needs, but default to HashMap for general-purpose key-value storage.