I have a List of Google PlaceSummary objects taken from the Google Places API. I'd like to collect and group them by their Google Place ID, but also retain the order of the elements. What I thought would work would be:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(
PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.mapping(PlaceSummary::getPlaceId, toList())
));
But it won't even compile. It looks like it should according to the Java API documentation on Collectors.
Previously I had this code:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId = places.stream()
.collect(Collectors.groupingBy(PlaceSummary::getPlaceId));
However standard .collect()
on the Streams API does not retain the order of elements in the subsequent HashMap
(obviously since HashMap
s are unordered). I wish for the output to be a LinkedHashMap
so that the Map is sorted by the insertion order of each bucket.
However, the solution I suggested doesn't compile. Firstly, it doesn't recognise the PlaceSummary::getPlaceId
since it says it's not a function - even though I know it is. Secondly, it says I cannot convert LinkedHashMap<Object, Object>
into M. M is supposed to be a generic collection, so it should be accepted.
How do I convert the List into a LinkedHashMap
using the Java Stream API? Is there a succinct way to do it? If it's too difficult to understand I may just resort to old school pre-Java 8 methods.
I noticed that there is another Stack Overflow answer on converting List to LinkedHashMap, but this doesn't have a solution I want as I need to collect 'this' the object I'm specifically iterating over.
You're really close to what you want:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(
PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.mapping(Function.identity(), Collectors.toList())
));
In the Collectors.mapping
method, you need to give the PlaceSummary
instance and not the place ID. In the code above, I used Function.identity()
: this collector is used to build the values so we need to accumulate the places themselves (and not their ID).
Note that it is possible to write directly Collectors.toList()
instead of Collectors.mapping(Function.identity(), Collectors.toList())
.
The code you have so far does not compile because it is in fact creating a Map<String, List<String>>
: you are accumulating the IDs for each ID (which is quite weird).
You could write this as a generic method:
private static <K, V> Map<K, List<V>> groupByOrdered(List<V> list, Function<V, K> keyFunction) {
return list.stream()
.collect(Collectors.groupingBy(
keyFunction,
LinkedHashMap::new,
Collectors.toList()
));
}
and use it like this:
Map<String, List<PlaceSummary>> placesGroupedById = groupByOrdered(places, PlaceSummary::getPlaceId);