10

I don't understand why Map.compute() and Map.computeIfPresent() take BiFunction parameters as well as Map.computeIfAbsent() a Function:

V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

I'd expect an ordinary Function<? super V, ? extends V>, mapping the old value to a new value, resp. a Supplier<? extends V> for the new value. The caller already has the key (first argument) so the function or the supplier can already make use of it. All examples I found don't use the key. The reasons that come to my mind:

  • the key must be (effectively) final -- that's easy to manage
  • there are some fancy easy-to-use method references

But I don't believe these are viable reasons for this design. Do you have any ideas?

Holger
  • 285,553
  • 42
  • 434
  • 765
steffen
  • 16,138
  • 4
  • 42
  • 81
  • 2
    There's one less variable to capture in the lambda. – Sotirios Delimanolis Dec 09 '15 at 15:53
  • 1
    1) "The caller already has the key (first argument) so the function or the supplier can already make use of it." I don't see how you would take the key into account without having it as a parameter in the function. 2) "All examples I found don't use the key." Well, easier to ignore an unused parameter than to use a parameter that is not present. – tobias_k Dec 09 '15 at 16:01
  • @SotiriosDelimanolis is that expensive? – steffen Dec 09 '15 at 16:05
  • @tobias_k (1) just use it, (2) adding parameters without a purpose is a bad thing. so my question is: why is it not superfluous. – steffen Dec 09 '15 at 16:08
  • 2
    “The caller already has the key” … unless the BiFunction is a method reference, in which case the method won't know the key's value, unless that key is passed in as an argument. – VGR Dec 09 '15 at 16:08
  • @VGR yes. i wrote that. – steffen Dec 09 '15 at 16:09
  • 4
    @steffen: it can make the difference between capturing and noncapturing, see http://stackoverflow.com/a/27524543/2711488 – Holger Dec 09 '15 at 16:09
  • 2
    @VGR, sometimes having key parameter is just unnecessary burden. Without it we could write `map.computeIfAbsent(key, ArrayList::new).add(value)`, now we need to write `map.computeIfAbsent(key, k -> new ArrayList<>()).add(value)` introducing useless `k`. – Tagir Valeev Dec 10 '15 at 01:38
  • 2
    @Tagir Valeev: it’s indeed more often the case that I don’t need the key than that I need it. However, in my projects, certain function transformation utility methods are part of the standard toolset as they come in handy at various places, i.e. I can use `map.computeIfAbsent(key, dropArg(ArrayList::new)). …` – Holger Dec 10 '15 at 09:05

1 Answers1

12

You may see computeIfPresent as the single-entry pendant of replaceAll whereas the latter requires the key as a parameter, but it’s natural to support the same function as input to both operations and the API is consistent here: it always provides the key as parameter to the function.

Generally, providing the key raises the reusability of existing functions, be it method references or ordinary class implementations (i.e. non-lambda) of the BiFunction interface. But this reusability may also affect the performance of lambda expressions given the existing JRE implementation:

As described here, lambda expressions capturing values from the surrounding context may end up in individual instances for each capturing process, whereas lambda expressions only using their parameters (non-capturing lambdas) will end up as a singleton instance. In contrast, having an otherwise unused parameter has no performance impact. So receiving the key as a parameter is preferable for that reason as well.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765