5

I'm looking for the best way (readability and efficiency) of providing a default value for a HashMap get operation but to also have the underlying map updated with that default value if a value is not already present.

I understand there are 3rd party libraries out there, but I'd prefer to stick with standard Java. Here are some different ways I've thought of:

Old school

Map<String, List<Integer>> someIntegerListLookup = new HashMap<>();
String key = "key";
...
List<Integer> integerList = someIntegerListLookup.get(key);

if (integerList == null) {
    integerList = new ArrayList<>();
    someIntegerListLookup.put(key, integerList);
}

Java 8 Map methods

// getOrDefault
List<Integer> integerList = someIntegerListLookup.getOrDefault(key, new ArrayList<>());
someIntegerListLookup.put(key, integerList);

// putIfAbsent
someIntegerListLookup.putIfAbsent(key, new ArrayList<>());
List<Integer> integerList = someIntegerListLookup.get(key);

Using Java 8 Optionals

List<Integer> integerList = 
    Optional.ofNullable(someIntegerListLookup.putIfAbsent(key, new ArrayList<>()))
    .orElse(someIntegerListLookup.get(key));

Not a fan of this approach from a readability point of view.

Using an 'or else' supplier:

List<Integer> integerList = Optional.ofNullable(someIntegerListLookup.get(key))
    .orElseGet(() -> {
        // Only runs if the map contains no value for the key
        List<Integer> defaultIntegersList = new ArrayList<>();
        someIntegerListLookup.put(key, defaultIntegersList);
        return defaultIntegersList;
    });

Right now I'm leaning towards the above solution being the best option. Are there other (better) solutions out there for this that I haven't thought of or come across?

Mureinik
  • 297,002
  • 52
  • 306
  • 350
Stephen Asherson
  • 1,557
  • 14
  • 23

2 Answers2

5

You don't want to create a new ArrayList unnecessarily if the key is present.

Therefore use :

List<Integer> integerList = someIntegerListLookup.computeIfAbsent(key, s -> new ArrayList<>());
Eran
  • 387,369
  • 54
  • 702
  • 768
  • 1
    The second example doesn't compile. See the discussion I'm having with Andreas on [my answer](http://stackoverflow.com/a/41375974/2422776). – Mureinik Dec 29 '16 at 08:46
  • 1
    There's a fundamental difference between `putIfAbsent()` and `computeIfAbsent()`: the former returns null in the absent case, which isn't what OP wants. Also, `ArrayList::new` wont compile unless `key` is `Integer`, in which case you'd be better off if it didn't compile. – shmosel Dec 29 '16 at 08:47
  • 1
    @Mureinik My bad, I didn't read the signature of that method carefully. Will fix. – Eran Dec 29 '16 at 08:49
  • 1
    @shmosel I missed that. Thanks for the comment. – Eran Dec 29 '16 at 08:52
5

The computeIfAbsent method is designed for exactly this purpose:

List<Integer> value = 
    someIntegerListLookup.computeIfAbsent("a", x -> new ArrayList<>());
Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • As you can see in [answer by Eran](http://stackoverflow.com/a/41375969/5221149), you can use a method reference instead of the lambda expression. – Andreas Dec 29 '16 at 08:42
  • 3
    @Andreas This is wrong. The mapping function parameter takes the key as an argument. Since `ArrayList` doesn't have a constructor from `String`, using `ArrayList::new` isn't appropriate here. You have to use a lambda, or something equivilant. – Mureinik Dec 29 '16 at 08:45