Java Map computeIfAbsent computeIfPresent and putIfAbsent

Simplify Your Java Code: How to Use computeIfPresent and computeIfAbsent for Cleaner Maps

Discover a cleaner way to manage Java maps using computeIfPresent and computeIfAbsent methods. Learn about these methods, simplify your code, and explore the differences with the putIfAbsent method.

Introduction

Java developers often encounter the challenge of managing data in maps efficiently. Traditional approaches involve cluttered code with null checks and containsKey() calls. In this guide, we’ll explore a cleaner and more efficient way to handle maps using computeIfPresent and computeIfAbsent methods that are present in Java’s java.util.Map interface. We’ll also compare these methods with putIfAbsent to give you a clear understanding of when to use each.

The Common Issue

Before we dive into a cleaner approach, let’s revisit the everyday challenge when working with maps. Imagine you’re managing a library, and you need to handle books in different categories. You often find yourself checking if a category exists in the library, fetching the list of books, and then adding or changing the list if it doesn’t exist. This pattern can make your code bulky and less reader-friendly.

Example: The Traditional Way

Map <String, List<String>> library = new HashMap<>();

List <String> scienceBooks = library.get("Science");
if (scienceBooks == null) {
    scienceBooks = new ArrayList<>();
    scienceBooks.add("Physics");
    library.put("Science", scienceBooks);
}
Example.java

In this example, we’re checking if the list of science books is null, and if it is, we’re creating a new list and adding it to the library. It’s not the most elegant or readable code.



The Solution: computeIfPresent & computeIfAbsent

Now, let’s introduce the cleaner solution: computeIfPresent and computeIfAbsent. These methods are part of the Java Map API and can make your code cleaner and more efficient.

a. computeIfPresent

The computeIfPresent method is used to compute a new value for an existing key in the map if that key is present. The method takes two parameters: the key for which the computation should be performed and a lambda expression to compute the new value.

Example: Updating a value in a map if the key is present

Map<String, List<String>> library = new HashMap<>();
List<String> books = new ArrayList<>();

books.add("Physics");
library.put("Science", books);

System.out.println("Library Before Update: " + library);

List<String> updatedBooks = library.computeIfPresent("Science", (key, bookList) -> {
  bookList.add("Chemistry");
  return bookList;
});

System.out.println("Books: " + updatedBooks);
System.out.println("Updated Library: " + library);
Example.java

Output:

Library Before Update: {Science=[Physics]}
Books: [Physics, Chemistry]
Updated Library: {Science=[Physics, Chemistry]}
Output

In this example, it checks if the category “Science” exists, and if it does, it directly adds the book “Physics” to the existing list. This approach is both concise and efficient.

Handling Null Values

In the case of computeIfPresent, if the lambda expression modifies the existing value to be null, the key-value pair is removed from the map. This ensures that null values do not persist in the map.

Example:

Map<String, List<String>> library = new HashMap<>();
List<String> books = new ArrayList<>();

books.add("Physics");
library.put("Science", books);

  System.out.println("Library Before Update: " + library);

List<String> updatedBooks = library.computeIfPresent("Science", (key, existingBooks) -> null);

System.out.println("Books: " + updatedBooks);
System.out.println("Updated Library: " + library);
Example.java

Output:

Library Before Update: {Science=[Physics]}
Books: null
Updated Library: {}
Output


b. computeIfAbsent

The computeIfAbsent method computes a new value for a given key only if that key is not present in the map. This method also takes two parameters: the key for which the computation should be performed and a lambda expression to compute the new value.

Example: Adding a new key-value pair to a map if the key is absent

Map <String, List<String>> library = new HashMap<>();

System.out.println("Library Before Update: " + library);

library.computeIfAbsent("Science", key -> new ArrayList<>()).add("Physics");
        
System.out.println("Updated Library: " + library);
Example.java

Output:

Library Before Update: {}
Updated Library: {Science=[Physics]}
Output

In this example, computeIfAbsent checks if the category “Science” exists in the library. If the category is not present, it is automatically created, and the book “Physics” is added to the newly created category. If “Science” already exists, this operation will not change the existing list. This approach is both concise and efficient.

Handling Null Values

computeIfAbsent ensures that if the lambda expression used for computation results in a null value, this value will not be added to the map. This is important in preventing the insertion of null values into the map.

Example:

Map<String, List<String>> library = new HashMap<>();

System.out.println("Library Before Update: " + library);
        
List<String> books = library.computeIfAbsent("Science", key -> null);

System.out.println("Books: " + books);
System.out.println("Updated Library: " + library);
Example.java

Output:

Library Before Update: {}
Books: null
Updated Library: {}
Output


Handling Exceptions

In both computeIfAbsent and computeIfPresent, when an exception occurs within the lambda expression, the behavior depends on how the exception is handled:

  • Exception Not Handled: If the exception is not handled within the lambda expression, it will be rethrown from the lambda expression. As a result, the exception will propagate further in the program.
  • Exception Handled Properly: If the exception is encountered within the lambda expression and handled gracefully, changes can still be made to the map despite the exception. The map will be updated as intended, and the exception will be caught and processed within the lambda expression.

Handling exceptions properly within the lambda expression is crucial to ensure the desired behavior of your code, whether you’re using computeIfAbsent or computeIfPresent.

putIfAbsent vs. computeIfAbsent vs. computeIfPresent

Sr. No.MethodputIfAbsentcomputeIfAbsentcomputeIfPresent
1.PurposeEnsures that the key exists in the map, adding it if absent.Checks if the key is absent in the map and computes a new value if needed.Updates the value associated with a key if that key is present.
2.Key PresentReturns the previous value associated with the specified key, or null if there was no mapping for the key. (A null return can also indicate that the map previously associated null with the key, if the implementation supports null values).If the key is present, it only returns the existing value associated with the specified key. No operation is performed.Returns the new value associated with the specified key, or null if none.
3.Key AbsentAdds a new key-value pair if the key is absent and returns null.Returns the newly computed value associated with the specified key, or null if the computed value is null.Returns null, and no operation is performed.
4.Exception HandlingException handling support is not present.Exceptions handling can be added while computing a value.Exception handling can be added while computing a value.
5.Return TypeReturns the previous value when the key is already present, returns null if the key is absent.Returns the newly computed value when the key is absent.Returns the updated value when the key is present.
6.Null ValuesNull values can be added if implementation supports.Automatically prevents null values from being stored in the map.Automatically removes the key if the updated value is null.
putIfAbsent vs. computeIfAbsent vs. computeIfPresent


Best Practices

  • Thread Safety: If you are working in a multithreaded environment, consider using a thread-safe map implementation like ConcurrentHashMap to avoid synchronization issues.
  • NullPointerException: Ensure that your lambda expressions handle null values properly. If the lambda attempts to perform operations on null values, a NullPointerException will be thrown.
  • Performance: Be mindful of the performance implications of these methods. They are powerful, but excessive use could impact the efficiency of your code.
  • Exception Handling: Since these methods allow lambda expressions that may throw exceptions, it’s important to handle exceptions appropriately within your lambda functions to prevent unexpected behavior in your application.
  • Key and Value Types: Ensure that the types of keys and values in your map align with the expected types in your lambda expressions. Mismatches can lead to runtime errors.
  • Readability: While these methods can lead to more concise code, consider the readability of your codebase. Clear and meaningful lambda expressions can make your code easier to understand and maintain.

FAQs

When should I use computeIfPresent and computeIfAbsent?

Use computeIfPresent when you want to update an existing value based on a key, and use computeIfAbsent when you want to add a value to the map only if the key is not present.

Can I use these methods with other map implementations like TreeMap or LinkedHashMap?

Yes, these methods can be used with any class that implements the java.util.Map interface, such as TreeMap, LinkedHashMap, or ConcurrentHashMap.

What happens if my lambda expression in computeIfPresent or computeIfAbsent throws an exception?

If the lambda expression in either method throws an exception, the exception will be propagated if not handled.

What’s the difference between using computeIfPresent and a combination of get and put for updating values?

The primary difference is that computeIfPresent combines the get-and-update operation atomically, making it more efficient and thread-safe than the separate get and put operations.

Are these methods suitable for handling large maps with many entries?

While computeIfPresent and computeIfAbsent can handle large maps, extensive usage on large maps may impact performance. It’s important to consider the potential performance overhead, especially when dealing with substantial data sets.

Conclusion

In conclusion, the computeIfPresent and computeIfAbsent methods are useful for simplifying and streamlining code while enhancing its efficiency. Whether you’re updating existing values or adding new key-value pairs, these methods offer a concise and readable way to achieve your goals. However, it’s essential to be mindful of considerations like thread safety, exception handling, and potential performance impacts, especially in complex or multi-threaded applications. By leveraging these methods thoughtfully, you can make your Java code cleaner and more efficient, ultimately improving the quality and maintainability of your projects.

Learn More

#

Interested in learning more?

Check out our blog on understanding Spring Bean Life Cycle.

Top Picks for Learning Java

Explore the recommended Java books tailored for learners at different levels, from beginners to advanced programmers.

Disclaimer: The products featured or recommended on this site are affiliated. If you purchase these products through the provided links, I may earn a commission at no additional cost to you.

1
Java: The Complete Reference
13th Edition

Java: The Complete Reference

  • All Levels Covered: Designed for novice, intermediate, and professional programmers alike
  • Accessible Source Code: Source code for all examples and projects are available for download
  • Clear Writing Style: Written in the clear, uncompromising style Herb Schildt is famous for
2
Head First Java: A Brain-Friendly Guide

Head First Java: A Brain-Friendly Guide

  • Engaging Learning: It uses a fun approach to teach Java and object-oriented programming.
  • Comprehensive Content: Covers Java's basics and advanced topics like lambdas and GUIs.
  • Interactive Learning: The book's visuals and engaging style make learning Java more enjoyable.
3
Modern Java in Action: Lambdas, streams, functional and reactive programming
2nd Edition

Modern Java in Action: Lambdas, streams, functional and reactive programming

  • Latest Java Features: Explores modern Java functionalities from version 8 and beyond, like streams, modules, and concurrency.
  • Real-world Applications: Demonstrates how to use these new features practically, enhancing understanding and coding skills.
  • Developer-Friendly: Tailored for Java developers already familiar with core Java, making it accessible for advancing their expertise.
4
Java For Dummies
8th Edition

Java For Dummies

  • Java Essentials: Learn fundamental Java programming through easy tutorials and practical tips in the latest edition of the For Dummies series.
  • Programming Basics: Gain control over program flow, master classes, objects, and methods, and explore functional programming features.
  • Updated Coverage: Covers Java 17, the latest long-term support release, including the new 'switch' statement syntax, making it perfect for beginners or those wanting to brush up their skills.

Add a Comment

Your email address will not be published.