How do I keep the iteration order of a List when using Collections.toMap() on a stream?
The 2-parameter version of Collectors.toMap()
uses a HashMap
:
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)
{
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
To use the 4-parameter version, you can replace:
Collectors.toMap(Function.identity(), String::length)
with:
Collectors.toMap(
Function.identity(),
String::length,
(u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
},
LinkedHashMap::new
)
Or to make it a bit cleaner, write a new toLinkedMap()
method and use that:
public class MoreCollectors
{
public static <T, K, U> Collector<T, ?, Map<K,U>> toLinkedMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)
{
return Collectors.toMap(
keyMapper,
valueMapper,
(u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
},
LinkedHashMap::new
);
}
}
How to ensure order of processing in java8 streams?
You are asking the wrong question. You are asking about sequential
vs. parallel
whereas you want to process items in order, so you have to ask about ordering. If you have an ordered stream and perform operations which guarantee to maintain the order, it doesn’t matter whether the stream is processed in parallel or sequential; the implementation will maintain the order.
The ordered property is distinct from parallel vs sequential. E.g. if you call stream()
on a HashSet
the stream will be unordered while calling stream()
on a List
returns an ordered stream. Note that you can call unordered()
to release the ordering contract and potentially increase performance. Once the stream has no ordering there is no way to reestablish the ordering. (The only way to turn an unordered stream into an ordered is to call sorted
, however, the resulting order is not necessarily the original order).
See also the “Ordering” section of the java.util.stream
package documentation.
In order to ensure maintenance of ordering throughout an entire stream operation, you have to study the documentation of the stream’s source, all intermediate operations and the terminal operation for whether they maintain the order or not (or whether the source has an ordering in the first place).
This can be very subtle, e.g. Stream.iterate(T,UnaryOperator)
creates an ordered stream while Stream.generate(Supplier)
creates an unordered stream. Note that you also made a common mistake in your question as forEach
does not maintain the ordering. You have to use forEachOrdered
if you want to process the stream’s elements in a guaranteed order.
So if your list
in your question is indeed a java.util.List
, its stream()
method will return an ordered stream and filter
will not change the ordering. So if you call list.stream().filter() .forEachOrdered()
, all elements will be processed sequentially in order, whereas for list.parallelStream().filter().forEachOrdered()
the elements might be processed in parallel (e.g. by the filter) but the terminal action will still be called in order (which obviously will reduce the benefit of parallel execution).
If you, for example, use an operation like
List<…> result=inputList.parallelStream().map(…).filter(…).collect(Collectors.toList());
the entire operation might benefit from parallel execution but the resulting list will always be in the right order, regardless of whether you use a parallel or sequential stream.
Java stream map and collect - order of resulting container
Yes, you can expect this even if you are using parallel stream as long as you did not explicitly convert it into unordered()
mode.
The ordering never changes in sequential mode, but may change in parallel mode. The stream becomes unordered either:
- If you explicitly turn it into unordered mode via
unordered()
call - If the stream source reports that it's unordered (for example,
HashSet
stream is unordered as order is implementation dependent and you cannot rely on it) - If you are using unordered terminal operation (for example,
forEach()
operation or collecting to unordered collector liketoSet()
)
In your case none of these conditions met, thus your stream is ordered.
Why is it not in the order of key when converting key to uppercase even though I use LinkedHashMap
The fact that the input List
contains LinkedHashMap
s doesn't mean that the output will contain LinkedHashMap
s.
If you want LinkedHashMap
s, you must request them explicitly in the toMap()
call:
static List<Map<String,Object>> convertKeyCase (List<Map<String,Object>> list,int...s) {
return list.stream()
.map(m -> m.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(p -> s.length == 0 || s[0]==1 ? p.getKey().toUpperCase():p.getKey().toLowerCase(),
Map.Entry::getValue,
(v1,v2)->v1,
LinkedHashMap::new)))
.collect(Collectors.toList());
}
This changes the output to:
[{DATE=2021-06-03, TEMP=17-29°C, WEATHER=1, WIND=2-3}, {DATE=2021-06-04, TEMP=17-30°C, WEATHER=1, WIND=3-4}]
[{date=2021-06-03, temp=17-29°C, weather=1, wind=2-3}, {date=2021-06-04, temp=17-30°C, weather=1, wind=3-4}]
How to group element of the Stream in order to pick a single element based on its poperties
To achieve that, you can group the movies by year and by actor's name creating a nested map. And then process its entries.
It can be done with Collectors.groupingBy()
. You need to pass as the first argument a function (Movie::getYear
) that extracts a year, and that will be the key of the map.
As a downstream collector of the groupingBy
you need to apply flatMapping
to extract actors from a movie object. Note, flatMapping
expect as an argument a function takes a stream element (movie object) and returns a Stream
(of actor's names).
And then a downstream collector of the flatMapping
you need to apply groupingBy
again to create a nested map that will group movies by actor. Since only a number of movies is needed, counting()
has to be used as a downstream collector to reduce movie objects mapped to each actor to a number of these objects.
With that will be created an intermediate map of type Map<String, Map<String, Long>>
that represent the total count of movies by actor for each year.
The next step is to create a stream over the entry set and flatten the nested map (count of movies by actor) by wrapping every entry in the map with a new composed entry of type Map.Entry<String, Map.Entry<String, Long>>
that will contain a year, actor's name and a count.
And the last step is to find the entry with the maximum count.
For that operationmax()
has to applied on the stream. It expects a Comparator
and returns a result wrapped by an Optional
. I assume that resume is expected to be present, therefore the code below is a fail-fast implementation that will raise a NuSuchElementExeption
in case if the stream source is empty (no result will be found).
Comparator
is defined by using the static method comparingLong()
. It will produce a comparator based on the count of movies.
public static void main(String[] args) {
List<Movie> movies =
List.of(new Movie("The Avengers", "2011", List.of("Tom","Chris","Robert")),
new Movie("Sherlock Holmes", "2011", List.of("Robert","Harris","Murphy")),
new Movie("Spiderman", "2002", List.of("Tobey","William","Kirsten")));
Map.Entry<String, Map.Entry<String, Long>> result =
movies.stream()
.collect(Collectors.groupingBy(Movie::getYear,
Collectors.flatMapping(movie -> movie.getActorList().stream(),
Collectors.groupingBy(Function.identity(),
Collectors.counting()))))
.entrySet().stream()
.flatMap(entryYear -> entryYear.getValue().entrySet().stream()
.map(entryActor -> Map.entry(entryYear.getKey(),
Map.entry(entryActor.getKey(),
entryActor.getValue()))))
.max(Comparator.comparingLong(entry -> entry.getValue().getValue()))
.orElseThrow(); // NoSuchElementExeption will be thrown is the result wasn't found (if stream is empty)
System.out.println(result);
}
Output
2011=Robert=2
Stream doesn't preserve the order after grouping
Keep in mind, Collectors#groupingBy(Function)
will return a HashMap
, which does not guarantee order. If you want the ordering to be in the order that the aggregate identity (your i % 2 == 0
result) shows up, then you can use LinkedHashMap
:
.collect(Collectors.groupingBy(i -> i % 2 == 0, LinkedHashMap::new, Collectors.toList()))
Would return a LinkedHashMap<Boolean, List<SeatedTicketAssignment>>
(since your collector is grouped by a boolean). Additionally, since the list utilized by the collector is an ArrayList
, it should preserve the iteration order of the stream relative to the list.
How do I efficiently iterate over each entry in a Java Map?
Map<String, String> map = ...
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "/" + entry.getValue());
}
On Java 10+:
for (var entry : map.entrySet()) {
System.out.println(entry.getKey() + "/" + entry.getValue());
}
Related Topics
How to Avoid a Lot of If Else Conditions
Singleton Design Pattern: Pitfalls
How to Read Xml Response from a Url in Java
Compare One String with Multiple Values in One Expression
Understanding Spring @Configuration Class
What Is the Purpose of Defining a Package in a Java File
Java Singleton and Synchronization
Cannot Find Firefox Binary in Path. Make Sure Firefox Is Installed
Can Constructor Return a Null Object
What Is the Priority of Casting in Java
How to Use Mdc with Thread Pools
How to Disable or Bypass Hardware Graphics Acceleration(Prism) in Javafx
How to Pass Parameters to Anonymous Class
How to Tell If a Checkbox Is Selected in Selenium for Java
Please Explain About Insertable=False and Updatable=False in Reference to the JPA @Column Annotation
How to Convert List to JSON in Java