I want to create a Map
from a List
of Points
and have inside the map all entries from the list mapped with the same parentId such as Map<Long, List<Point>>
.
I used Collectors.toMap()
but it doesn't compile :
Map<Long, List<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.toMap(Point::getParentId, c -> c));
The groupingBy() method of Collectors class in Java are used for grouping objects by some property and storing results in a Map instance. In order to use it, we always need to specify a property by which the grouping would be performed. This method provides similar functionality to SQL's GROUP BY clause.
The toList() method of Collectors Class is a static (class) method. It returns a Collector Interface that gathers the input data onto a new list. This method never guarantees type, mutability, serializability, or thread-safety of the returned list but for more control toCollection(Supplier) method can be used.
The mapping() method mapping() is a static method of the Collectors class that returns a Collector . It converts a Collector accepting elements of one type to a Collector that accepts elements of another type. This method is generally used in multi-level reduction operations.
is a terminal operation (it does not return a Stream), so that operation will end the stream. Depending on what you want later to do inside the last map operation, you could create a custom collector that will "stay" inside the stream; even if inside you would probably still gather elements into a Map.
TLDR :
To collect into a Map
that contains a single value by key (Map<MyKey,MyObject>
), use Collectors.toMap()
.
To collect into a Map
that contains multiple values by key (Map<MyKey, List<MyObject>>
), use Collectors.groupingBy()
.
Collectors.toMap()
By writing :
chargePoints.stream().collect(Collectors.toMap(Point::getParentId, c -> c));
The returned object will have the Map<Long,Point>
type.
Look at the Collectors.toMap()
function that you are using :
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
It returns a Collector
with as result Map<K,U>
where K
and U
are the type of return of the two functions passed to the method. In your case, Point::getParentId
is a Long and c
refers to a Point
. Whereas the Map<Long,Point>
returned when collect()
is applied on.
And this behavior is rather expected as Collectors.toMap() javadoc states :
returns a
Collector
that accumulates elements into aMap
whose keys and values are the result of applying the provided mapping functions to the input elements.
But if the mapped keys contains duplicates (according to Object.equals(Object)
), an IllegalStateException
is thrown
It will be probably your case as you will group the Point
s according to a specific property : parentId
.
If the mapped keys may have duplicates, you could use the toMap(Function, Function, BinaryOperator)
overload but it will not really solve your problem as it will not group elements with the same parentId
. It will just provide a way to not have two elements with the same parentId
.
Collectors.groupingBy()
To achieve your requirement, you should use Collectors.groupingBy()
which the behavior and the method declaration suits much better to your need :
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier)
It is specified as :
Returns a Collector implementing a "group by" operation on input elements of type T, grouping elements according to a classification function, and returning the results in a Map.
The method takes a Function
.
In your case, the Function
parameter is Point
(the type
of Stream) and you return Point.getParentId()
as you want to group elements by parentId
values.
So you could write :
Map<Long, List<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.groupingBy( p -> p.getParentId()));
Or with a method reference :
Map<Long, List<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.groupingBy(Point::getParentId));
Collectors.groupingBy() : go further
Indeed the groupingBy()
collector goes further than the actual example. The Collectors.groupingBy(Function<? super T, ? extends K> classifier)
method is finally just a convenient method to store the values of the collected Map
in a List
.
To store values of the Map
in another thing than a List
or to store the result of a specific computation , groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
should interest you.
For example :
Map<Long, Set<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.groupingBy(Point::getParentId, toSet()));
So beyond the asked question, you should consider groupingBy()
as a flexible way to choose values that you want to store into the collected Map
, what definitively toMap()
is not.
Collectors.groupingBy
is exactly what you want, it creates a Map from your input collection, creating an Entry using the Function
you provide for it's key, and a List of Points with your associated key as it's value.
Map<Long, List<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.groupingBy(Point::getParentId));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With