Suppose we have a Stream
of String
objects where two sequential values belong together as a pair. We want to transform the stream into a List
where each pair is transformed into a Map
object with a key and value. We can write a custom Collector
that stores the first String
value of a pair. When the next element in the Stream
is processed by the Collector
a Map
object is created with the stored first value and the new value. The new Map
is added to the result List
.
In the next example we write the ListMapCollector
to transform a Stream
of paired String
values into a List
of Map
objects:
package mrhaki.streams; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<Map<String, String>> pairs = Stream.of("language", "Java", "username", "mrhaki") .collect(new ListMapCollector()); // Result is list of maps: [{language=Java},{username=mrhaki}] assert pairs.size() == 2; assert pairs.get(0).get("language").equals("Java"); assert pairs.get(1).get("username").equals("mrhaki"); } private static class ListMapCollector implements Collector<String, List<Map<String, String>>, List<Map<String, String>>> { private String key; /** * @return An empty list to add our Map objects to. */ @Override public Supplier<List<Map<String, String>>> supplier() { return () -> new ArrayList<>(); } /** * @return Accumulator to add Map with key and value to the result list. */ @Override public BiConsumer<List<Map<String, String>>, String> accumulator() { return (list, value) -> { if (key != null) { list.add(Map.of(key, value)); key = null; } else { key = value; } }; } /** * @return Combine two result lists into a single list with all Map objects. */ @Override public BinaryOperator<List<Map<String, String>>> combiner() { return (list1, list2) -> { list1.addAll(list2); return list1; }; } /** * @return Use identity function to return result. */ @Override public Function<List<Map<String, String>>, List<Map<String, String>>> finisher() { return Function.identity(); } /** * @return Collector characteristic to indicate finisher method is identity function. */ @Override public Set<Characteristics> characteristics() { return Set.of(Characteristics.IDENTITY_FINISH); } } }
Another solution could be to turn the Stream
with values into a single Map
. Each pair of values is a key/value pair in the resulting Map
:
package mrhaki.streams; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Stream; public class Main { public static void main(String[] args) { Map<String, String> map = Stream.of("language", "Java", "username", "mrhaki") .collect(new MapCollector()); // Result is map: {language=Java,username=mrhaki} assert map.size() == 2; assert map.get("language").equals("Java"); assert map.get("username").equals("mrhaki"); } private static class MapCollector implements Collector<String, Map<String, String>, Map<String, String>> { private String key; /** * @return An empty map to add keys with values to. */ @Override public Supplier<Map<String, String>> supplier() { return () -> new HashMap<>(); } /** * @return Accumulator to add key and value to the result map. */ @Override public BiConsumer<Map<String, String>, String> accumulator() { return (map, value) -> { if (key != null) { map.put(key, value); key = null; } else { key = value; } }; } /** * @return Combine two result maps into a single map. */ @Override public BinaryOperator<Map<String, String>> combiner() { return (map1, map2) -> { map1.putAll(map2); return map1; }; } /** * @return Use identity function to return result. */ @Override public Function<Map<String, String>, Map<String, String>> finisher() { return Function.identity(); } /** * @return Collector characteristic to indicate finisher method is identity function. */ @Override public Set<Characteristics> characteristics() { return Set.of(Characteristics.IDENTITY_FINISH); } } }
Written with Java 15.0.1.