Among the below two code snippets - which one is preferable.
Approach -1
public Map<String, List<MyObj>> approach1(
Iterable<? extends String> keys) {
return StreamSupport.stream(keys.spliterator(), false)
.map(dt -> new AbstractMap.SimpleEntry<>(dt, load(dt)))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, HashMap::new));
}
Approach - 2
public Map<String, List<MyObj>> approach2(
Iterable<? extends String> keys) {
return StreamSupport.stream(keys.spliterator(), false)
.collect(toMap(dt -> dt, k -> load(dt), (a, b) -> a, HashMap::new));
}
In approach1, a map entry is explicitly created and then mapped in toMap
using getKey
and getValue
, does this have any advantage or approach2 which is more direct is equally good.
Followup Question: If the entries in Iterable keys
or not sorted, does the merge function (a, b) -> a
give same result for the same entries in keys
but in different order.
If the entries in Iterable keys or not sorted, does the merge function
(a, b) -> a
give same result for the same entries in keys but in different order.
It has nothing to do with sorting.
Where behavior of the mergeFunction is deterministic or not depends on whether the source is ordered or not (ordered simply means predictable order of iteration, elements might not be sorted anyhow). If it's the case, Spliterator
produced from the given Iterable
would have a characteristic Spliterator.ORDERED
.
That would dictate if the resulting Stream would be ordered or unordered.
For instance, HashSet
has no particular order of iteration, opposed to collections like ArrayList
, ArrayDeque
, etc. guarantee deterministic order of iteration.
Note: certain methods offered by Stream API that can change the ordering of the pipeline. Operation unordered()
will loosen ordering constraint (if the stream was ordered), and sorted()
would impose ordering.
According to the documentation of toMap()
:
mergeFunction - a merge function, used to resolve collisions between values associated with the same key, as supplied to
Map.merge(Object, Object, BiFunction)
So in the ordered stream, mergeFunction (a, b) -> a
would preserve the value that has been produced from the stream element that occur earlier in the stream. But if the stream is unordered, elements can be consumed by the Collector in any order, and it would be not possible to predict which value would end up in the resulting Map.
Regarding the question about the difference between the two streams, map() operation in the first snippet is redundant, since it only creates unnecessary intermediate Map.Entry
instances. It looks like you've added this operation just for the purpose of to be able to apply method references in the Collector, it's not justifiable. Object creation is not happening for free, and it might not be obvious for the reader of the code why do you need this intermediate transformation.
Also, note specifying mapFactory HashMap::new
inside the Collector in both snippets is pointless because a general purpose implementation of the Map
interface in the form of HashMap
or something that might come as its recommended replacement in the future would be given to you by default. So mapFactory can be omitted if you're fine with getting a HashMap
a result of the stream execution. The use case for four-args version of toMap
expecting mapFactory is when you need a TreeMap
, EnumMap
, or any other special purpose implementation.