HashMap Operations in Java: A Complete Guide

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

MethodDescriptionExample
put(K key, V value)Inserts or updates a key-value pairmap.put("Alice", 95);
putIfAbsent(K key, V value) (Java 8+)Inserts only if key is not presentmap.putIfAbsent("Bob", 80);

Note: If the key already exists, put() replaces the old value.

B. Retrieving Values

MethodDescriptionExample
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 missingint s = map.getOrDefault("Eve", 0);

Caution: get() returns null for both missing keys and actual null values.

C. Removing Entries

MethodDescriptionExample
remove(Object key)Removes and returns value for keymap.remove("Alice");
remove(Object key, Object value) (Java 8+)Removes only if key maps to specified valuemap.remove("Alice", 95);

D. Checking Existence

MethodDescriptionExample
containsKey(Object key)Checks if key existsboolean 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 entriesif (map.isEmpty()) { ... }
size()Returns number of key-value pairsint 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

OperationAverage Time ComplexityWorst 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)
IterationO(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 prevent hashCode() changes.
  • Override hashCode() and equals() 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 ConcurrentHashMap for 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 null keys carelessly: Only one null key is allowed; get(null) may be ambiguous.
  • Ignoring thread safety: HashMap is not synchronized—use ConcurrentHashMap in multi-threaded code.
  • Assuming order: HashMap does not maintain insertion order (use LinkedHashMap if needed).

8. HashMap vs. Other Map Implementations

FeatureHashMapLinkedHashMapTreeMapConcurrentHashMap
OrderNo orderInsertion/access orderSorted (natural/custom)No order
Null KeysOneOneNot allowedOne (but discouraged)
Thread-SafeNoNoNo✅ Yes
Time ComplexityO(1) avgO(1) avgO(log n)O(1) avg
Use CaseGeneral-purposePredictable iterationSorted keysConcurrent 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper