Yes, the final example code reads reasonably nicely, but alas -- unless ConcurrentHashMap.merge() has been rewritten since Java 8 time -- it is definitely not as performant as the author appears to think it is since the actions will be contained in a fairly long synchronized block. This is not to say the code is bad, but you can't get around that.<p>Somewhat surprisingly, the code the author pooh-poohs is actually more performant (although it's better if the test is for the presence of the key rather than the "putIfPresent/computeIfAbsent combination) because the JVM will properly optimize a membership test (in keyset) and the use of a ternary expression makes the action clear.
yes, Java 8 adds a bunch of methods to ArrayList and HashMap that makes the code a little more high level.<p>There is also another variation, using map.getOrDefault()<p><pre><code> words.forEach(word -> {
map.put(word, map.getOrDefault(word, 0) + 1);
});
</code></pre>
and then at some points you discover the Stream API<p><pre><code> var map = words.stream().collect(Collectors.groupingBy(w -> w, Collectors.counting()));</code></pre>
This is a poor semantics. The function should always be called. In the non-existent-key case, it should be invoked on the default value that is supplied. Thus the example of the histogram tabulation would use 0 as the initial value.<p>The name is ill-chosen; this mutator doesn't "merge" at all; it "updates" the map entry through a function, with a default value.<p>"merge" might be a somewhat good name for an operation that combines two maps, with a function that resolves key clashes; though since that is a kind of set union, that provides better terminology.
I like how the more mathematical concepts get their way to the mainstream languages. It's making concepts such as monoids and semigroups more accessible to general public.
The one thing that frustrates me about merge is that it returns the value inserted, rather than the value being replaced (as Map::put does).<p>There are good uses cases for what it does - but there are also good use cases for returning the previous value, and when i have one of those, i can't use merge, and have to fall back to a get followed by a put.<p>Perhaps what i would really like is a Rust-style entry API, where i could use a key to obtain a Map.Entry, which i could then inspect and mutate how i liked. It might look like:<p><pre><code> Entry<K, V> getOrCreateEntry(K key, Function<? super K, ? extends V> mappingFunction)
</code></pre>
And then i could write:<p><pre><code> void sell(Potato) throws FarmException { ...}
Map<Field, Potato> potatoesByField = new HashMap<>();
for (var potato: getAllPotatoes()) {
var fieldAndPotato = potatoesByField.getOrCreateEntry(potato.getField(), f -> potato);
var oldPotato = fieldAndPotato.getValue();
if (potato.getWeight() > oldPotato.getWeight()) {
potatoAndField.setValue(potato);
sell(oldPotato);
} else {
sell(potato);
}
}
</code></pre>
To collect my prize potatoes into a map, and sell the rest. Still ugly, but hey, at least it's not Go.