16

So I found a curiosity with Java's Map computeIfAbsent (using java8) method and I hope someone can tell me why this is happening as I can not really follow the logic behind that issue.

So, I have a Map with a key (obviously) and the value is a list and I use the computeIfAbsent to create a new list when a key is not set yet. Now when I use an Integer as key I can use the following:

List<Object> list = map.computeIfAbsent(1, ArrayList::new);

But when I use a String as key trying to use

List<Object> list = map.computeIfAbsent("key", ArrayList::new);

I get the error that The method computeIfAbsent(String, Function<? super String,? extends List<Object>>) in the type Map<String,List<Object>> is not applicable for the arguments (String, ArrayList::new). Is there just the implementation missing? Using a String key I have to use the method like that, which is then working again.

List<Object> list = map.computeIfAbsent("key", k -> new ArrayList<>());

Maybe someone can enlighten me about that. Thanks :)

Fussel
  • 1,740
  • 1
  • 8
  • 27

2 Answers2

20

The mapping function - Function<? super K, ? extends V> mappingFunction - maps a key to a value, so when the key is Integer, ArrayList::new works, since ArrayList has a constructor that takes an int (the initial capacity). On the other hand, it doesn't have a constructor that takes a String.

Since the key probably shouldn't affect the initial capacity of the ArrayList, you should not use a method reference here (in both cases). Use lambda expression.

To make it clearer:

List<Object> list = map.computeIfAbsent(1, ArrayList::new);

behaves similar to:

List<Object> list = map.computeIfAbsent(1, k -> new ArrayList<>(k));

so it will create an ArrayList with initial capacity of 1.

On the other hand:

List<Object> list = map.computeIfAbsent("key", ArrayList::new);

behaves similar to:

List<Object> list = map.computeIfAbsent("key", k -> new ArrayList<>(k));

where k is a String, so it doesn't pass compilation.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 2
    And more specifically, `1 -> ArrayList::new` does not in fact produce a list with the value `1` inserted. – chrylis -cautiouslyoptimistic- Dec 19 '18 at 07:30
  • So when using `ArrayList::new` the given key is inserted into the list? That in my example I'd have a List with the value 1 already in it? – Fussel Dec 19 '18 at 07:33
  • @chrylis did the OP expect such behavior (1 being inserted into the List)? I didn't see that mentioned in the question. – Eran Dec 19 '18 at 07:33
  • @Fussel no, chrylis's comment said it "does not" insert 1 into the list. – Eran Dec 19 '18 at 07:34
  • Ah, I see but as I don't want the key being part of the list I'll use the lambda expression. If the generated list is `List` it does also work the a string as key. – Fussel Dec 19 '18 at 07:40
  • 1
    @Fussel the key will **not** be part of the list either way, but you are correct to use lambda expressions in both cases (Integer and String key). No, the `List` being `List` makes no difference, since the key is not added to the List, it is passed to the `ArrayList` constructor (which doesn't work for Strings, since ArrayList has no constructor that accepts a String). – Eran Dec 19 '18 at 07:45
  • 2
    @Fussel If you replace `1` with 1 billion you might get a memory error since Java will try to allocate an array that has the capacity of storing 1 billion elements. Note that capacity != actual elements. An empty `ArrayList` can consume an arbitrary amount of memory depending on the capacity. – Giacomo Alzetta Dec 19 '18 at 15:08
9

Your second attempt

List<Object> list = map.computeIfAbsent("key", ArrayList::new);

would actually be equal to

List<Object> list = map.computeIfAbsent("key", k -> new ArrayList<>(k));

and since ArrayList has no constructor taking a String as a parameter, it doesn't work. The first example works as it creates a list with one element.

Jan Gassen
  • 3,406
  • 2
  • 26
  • 44