יום שלישי, 26 באפריל 2016

computeIfAbsent() is your new friend for Map in Java

I had a case today I had to populate a map with that signature:

Map<Long, Map<Integer, Integer>> myMap = Maps.newHashMap();

The way I populate it is like that:


resultSet -> {
  resultSet.beforeFirst();
  while (resultSet.next()) {
    Integer count = ...;
    Integer reason = ...;
    Long id = ...;
    //now I have to add the values to myMap
  }
})

prior to Java 8 this is one way to do it:

Map<Integer, Integer> internalMap = myMap.get(id);
if (internalMap == null) {
  internalMap = Maps.newHashMap();
  myMap.put(id, internalMap);
}
internalMap.put(reason, count);


It is a boilerplate oriented code, did it many times in the past. Now with Java 8 it is possible to do something simpler:
How about that? (Hint: Not working)

myMap.getOrDefault(id, Maps.newHashMap()).put(reason, count);

Any guess what's the mistake here?
getOrDefault just returns another value, do not put it on the map, so myMap remains empty.

Another try:

myMap.putIfAbsent(id, Maps.newHashMap()).put(reason, count);

And here I got NullPointerException. Apparently the Java api implementor decided to keep it similar to Map.put and return the previous value. Not a good decision if you ask me since this method should help you prevent working with nulls.

Finally the solution:

myMap.computeIfAbsent(id, key -> Maps.newHashMap()).put(reason, count);

Unlike putIfAbsent, here the api returns the existing value or the new value, allowing you to continue working on it more fluently.