13

Java8 introduced those nice methods getOrDefault() and putIfAbsent(), allowing to write code like:

Map<Foo, List<Bar>> itemsByFoo = ...
List<Bar> bars = itemsByFoo.getOrDefault(key, new ArrayList<>());
bars.add(someNewBar);

Now I am wondering if there are good factual reasons to either do:

itemsByFoo.put(key, bars);

or

itemsByFoo.putIfAbsent(key, bars);

Both would work:

  • option 1 might do a lot of unnecessary "put" calls when adding elements to lists happens often
  • option2 might do a lot of unnecessary "containsKey" calls when adding new entries for new keys is dominant

SO: are the good reasons to go for option 1 or option 2 "always"?

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 13
    Ahem, *neither*. Use `itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>()) .add(someNewBar);` for the entire operation. – Holger Aug 02 '17 at 11:04
  • @Holger yes :) excellent point. Because `putIfAbsent` might return a `null` as it returns the *previous* value... Also `computeifAbsent` is present in java-8, not 7. I've faced this before... – Eugene Aug 02 '17 at 11:10
  • 2
    @Eugene: `putIfAbsent` was added to the `Map` interface in Java 8, as it was now possible with `default` methods, but it had to keep the contract of `ConcurrentMap.putIfAbsent`, which exists since Java 5, so it’s not as convenient as `computeIfAbsent`… – Holger Aug 02 '17 at 11:16
  • In both cases the putVal function is called and the following code is executed p = tab[i = (n - 1) & hash . p.Key is the value of the key. This key is compared with the Key value you supplied to be put in the HashMap. In both case contains is anyways called – user1428716 Aug 02 '17 at 11:20

2 Answers2

23

getOrDefault is suitable if you want to use a stand-in for an absent value without modifying the map. If you want to add a new value for absent keys, you can do it right in one operation.

List<Bar> bars = itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>());
bars.add(someNewBar);

or even

itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>()).add(someNewBar);

In the best case, when being overridden by the Map implementation, like with HashMap, this will bear a single hash lookup only.

Not that putIfAbsent only bears two lookups when using the default implementation, but, of course, most Map implementations will provide a single lookup implementation for it. Still, the combination of getOrDefault and putIfAbsent would still bear two lookups in the best case, whereas an optimized computeIfAbsent does only one.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • isn't the `itemsFoo.compute...; bars.add(someNewBar)` a race condition now? Since it's not a single atomic operation; someone could remove that entry by the time the `add` is done? I wonder if I'm hitting your patience limit today with so many following questions... – Eugene Aug 02 '17 at 11:22
  • 5
    @Eugene: this is a generic `Map` question. Being atomic was not a requirement. Otherwise, you have a lot more to do. While you can make the insertion thread safe doing everything inside `compute`, it has no relation to the code which eventually reads the `List` and there has to be code reading it, if the storage isn’t an end in itself, so for any real life case, additional effort will be needed anyway. – Holger Aug 02 '17 at 11:25
  • thank you. where in the world have I see `CHM` here? my bad. That's such a good point about `putIfAbsent` that does 2 look-ups in the default implementation... I noticed it just now that it does a `get` then a `put`. This would make a great example about why default methods are override. – Eugene Aug 02 '17 at 11:32
9

An important point about computeIfAbsent is that it takes a Function which will only get executed if the Key is absent and we need a default Value.

Whereas getOrDefault requires the default Value itself, already computed. In this case, the default Value we would need is a new ArrayList<Bar>(), which has the side effect of allocating a new object on the heap.

We want to defer doing that until we are sure that the key isn't already in itemsByFoo. Otherwise we would be generating unnecessary garbage for gc to collect.

John Volan
  • 91
  • 3