How to Use Java HashMap Effectively
Struggling with HashMaps? Here's how to effectively use Java HashMaps.
Join the DZone community and get the full member experience.
Join For FreeHashMap
is one of the most used data structures that we use in our day-to-day Java programming.
In this article, we are going to explain what, why, and how to use HashMap
in Java effectively. Before indulging you into the deep technicalities of theHashMap
, let’s first look at an example.
You have a grocery store and there are lots of items in your shop. The names and prices of these products vary. It can be difficult to remember all of these products. While you have written all these products down in a notebook, whenever you sell a product, you have to look at that notebook to find out the price, etc.
If the names are not alphabetically written in the notebook, then it takes a long time to find out every time. In the Algorithm class, you may have learned that this has a complexity of O(n). But if names are kept alphabetically, then a binary search can be used and it will take time (log n). You know that O (log n) takes less time than O(n).
Although O(log n) takes considerably less time, still, it takes some time. It would have been best if it would take no time at all. For this case, perhaps, you could memorize the names and prices of all the products and tell the price as soon as the buyer has a product name.
This exact scenario can be solved using HashMap
. We can put the name of the products and their price in a HashMap
andHashMap
can return the value with a key in almost no time.
Now, let’s define what a HashMap
is.HashMap
is a key-value data structure that provides constant time, O(1) complexity for both get and put operation.
Let's look at an example:
//create a map in java 11
var productPrice = new HashMap<String, Double>();
//or in java 8
Map<String, Double> productPrice = new HashMap<>();
// add value
productPrice.put("Rice", 6.9);
productPrice.put("Flour", 3.9);
productPrice.put("Sugar", 4.9);
productPrice.put("Milk", 3.9);
productPrice.put("Egg", 1.9);
Now, we need to access the price with the key (product name) from the map. For that, we have a method named get ()
.
//get value
Double egg = productPrice.get("Egg");
System.out.println("The price of Egg is: " + egg);
Now that we've covered the basics of HashMap
, let’s go deep and how can we use it.
Printing All Keys and Values From the HashMap
There are several ways to print all the keys and value from the hashmap. Let's see one by one -
1. We want to print all the keys:
Set<String> keys = productPrice.keySet();
//print all the keys
for (String key : keys) {
System.out.println(key);
}
Or
keys.forEach(key -> System.out.println(key));
I prefer theforEach
loop with the lambda expression as it is more concise.
2. We want to print all the following values:
Collection<Double> values = productPrice.values();
values.forEach(value -> System.out.println(value));
3. We want to print all the keys and values altogether, as shown below:
Set<Map.Entry<String, Double>> entries = productPrice.entrySet();
for (Map.Entry<String, Double> entry : entries) {
System.out.print("key: "+ entry.getKey());
System.out.println(", Value: "+ entry.getValue());
}
Or, we can use theforEach
lambda expression, which is more concise:
productPrice.forEach((key, value) -> {
System.out.print("key: "+ key);
System.out.println(", Value: "+ value);
});
We Want to Know if a Key Already Exists
Often times, it becomes the case that we first want to check if a key already exists or not. Based on the information, we must make a decision. For example, in dynamic programming, memorization is one of the techniques that we use most frequently. Let's check out an example.
The Fibonacci series is a famous series we use in case of demonstrating recursion. The formula of this series is as follows:
f(n) = f(n-1) + f(n-2)
However, in this example, the same value may get calculated over and over, which can be easily prevented if we use memoization.
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
public class Fibonacci {
private Map<Integer, BigInteger> memoizeHashMap = new HashMap<>();
{
memoizeHashMap.put(0, BigInteger.ZERO);
memoizeHashMap.put(1, BigInteger.ONE);
memoizeHashMap.put(2, BigInteger.ONE);
}
private BigInteger fibonacci(int n) {
if (memoizeHashMap.containsKey(n)) {
return memoizeHashMap.get(n);
} else {
BigInteger result = fibonacci(n - 1).add(fibonacci(n - 2));
memoizeHashMap.put(n, result);
return result;
}
}
public static void main(String[] args) {
Fibonacci fibonacci = new Fibonacci();
for (int i = 0; i < 100; i++) {
System.out.println(fibonacci.fibonacci(i));
}
}
}
In this code, we have first checked if a Fibonacci number is already available in the memoizeHashMap
or not. If it is already available, we don't need to calculate, we can just get it using the key. Otherwise, we can always calculate.
The above code will produce 100 Fibonacci number faster.
Note: the put()
method replaces the value with a key if the key already exists in the map.
Now, let's look into some of the methods available in HashMap
that can make our life easy.
Comparison: computeIfAbsent() Vs. puIfAbsent()
Now, let's revisit the fibonacci()
method from the previous code block again. We can make it shorter and more concise if we use the computeIfAbsent()
method instead of containsKey()
.
private BigInteger fibonacci(int n) {
return memoizeHashMap.computeIfAbsent(n,
(key) -> fibonacci(n - 1).add(fibonacci(n - 2)));
}
This method takes two arguments. One is the key and the other is a functional interface, which takes a key and, in turn, returns a value. The idea is, if the key exists in the map, it will return the value. Otherwise, it will compute the value and add it the map and then return the value. This makes the entire code much simpler and shorter.
However, there is another method named putIfAbsent()
that takes a value directly.
productPrice.putIfAbsent("Fish", 4.5);
Differences Between putIfAbsent() and computeIfAbsent()
computeIfAbsent()
takes a mapping function, that is called to obtain the value if the key is missing. On the other hand, putIfAbsent()
takes the value directly. So in case, the value of the key comes from a method call and if the method is the expensive one, then, the computeIfAbsent()
will not calculate the value unless the key is not found, where the putIfAbsent()
will calculate the value anyway.
var theKey = "Fish";
//Even though if the key is present, the callExpensiveMethodToFindValue will get called
productPriceMap.putIfAbsent(theKey, callExpensiveMethodToFindValue(theKey));
//The callExpensiveMethodToFindValue will never get called if key is already present
productPriceMap.computeIfAbsent(theKey, key -> callExpensiveMethodToFindValue(key));
Comparison: compute() Vs. computeIfPresent()
Similarly, HashMap
has methods named compute()
and computeIfPresent()
.
We all know Oracle donated Java EE to the Eclipse Foundation, and consequently, it was renamed Jakarta EE. Let's say we want to write a program where we feed articles about this topic and calculate the frequency of select words to see how often people are mentioning these words.
import java.util.HashMap;
import java.util.Map;
public class WordFrequencyFinder {
private Map<String, Integer> map = new HashMap<>();
{
map.put("Java", 0);
map.put("Jakarta", 0);
map.put("Eclipse", 0);
}
public void read(String text) {
for (String word : text.split(" ")) {
if (map.containsKey(word)) {
Integer value = map.get(word);
map.put(word, ++value);
}
}
}
}
The above code is too simple. We first checked if the key already exists in the map. If it exists, we can get the value and update it with an increment. This is the old way of writing code.
Java 8 brings a nicer method called computeIfPresent()
. Let's rewrite the read method with this method.
public void read(String text) {
for (String word : text.split(" ")) {
map.computeIfPresent(word, (String key, Integer value) -> ++value);
}
}
This above code is much nicer. The computeIfPresent()
method takes the key and a remapping function, which, in turn, computes the value only if the key is present. So, the remapping function is only called if the key is present in the map, otherwise not.
Furthermore, we have another method named compute()
, which takes similar arguments as computeIfPresent()
. However, it computes the remapping function and it doesn't matter if the key is present or not. Based on the remapping function's output, it adds the value to the map.
getOrDefault()
In some cases, it happens that the key we look at has a map that does not exist. However, we still want to have a value and we do not want to change the map. In such cases, we can use the methodgetOrDefault()
, which is very useful.
productPriceMap.getOrDefault("Fish", 29.4);
Conclusion
HashMap
makes the data access much easier and faster. And Java 8 and the introduction of lambda expressions make our life as developers much more simple. We can use the lambda expression to make our code shorter and concise.
If you enjoyed this article and want to learn more about Java Collections, check out this collection of tutorials and articles on all things Java Collections.
Opinions expressed by DZone contributors are their own.
Comments