Collect Elements of a Stream into a Collection in Java

Elements from a Stream can be easily collected into a container by using the Stream.collect operation:

Collect with toList() and toSet()

Elements from a Stream can be easily collected into a container by using the
Stream.collect operation:

System.out.println(Arrays
      .asList("apple", "banana", "pear", "kiwi", "orange")
      .stream()
      .filter(s -> s.contains("a"))
      .collect(Collectors.toList())
);
// prints: [apple, banana, pear, orange]

Other collection instances, such as a Set, can be made by using other Collectors built-in methods. For example, Collectors.toSet() collects the elements of a Stream into a Set.

Explicit control over the implementation of List or Set

According to documentation of Collectors#toList() and Collectors#toSet(), there are no guarantees on the type, mutability, serializability, or thread-safety of the List or Set returned.

Related Article: Finding Statistics about Numerical Streams in Java

For explicit control over the implementation to be returned, Collectors#toCollection(Supplier) can be used instead, where the given supplier returns a new and empty collection.

// syntax with method reference
System.out.println(strings
       .stream()
       .filter(s -> s != null && s.length() <= 3)      .collect(Collectors.toCollection(ArrayList::new)) ); 

// syntax with lambda 
System.out.println(strings .stream() .filter(s -> s != null && s.length() <= 3) .collect(Collectors.toCollection(() -> new LinkedHashSet<>()))
);

Collecting Elements using toMap

Collector accumulates elements into a Map, Where key is the Student Id and Value is Student Value.

  List students = new ArrayList();
       students.add(new Student(1,"test1"));
       students.add(new Student(2,"test2"));
       students.add(new Student(3,"test3"));

       Map IdToName = students.stream()
       .collect(Collectors.toMap(Student::getId,    Student::getName));
       System.out.println(IdToName);

Output :

{1=test1, 2=test2, 3=test3}

The Collectors.toMap has another implementation Collector> toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction).The mergeFunction is mostly used to select either new value or retain old value if the key is repeated when adding a new member in the Map from a list.

The mergeFunction often looks like: (s1, s2) -> s1 to retain value corresponding to the repeated key, or (s1, s2) -> s2 to put new value for the repeated key.

Collecting Elements to Map of Collections

Example: from ArrayList to Map>

Often it requires to make a map of list out of a primary list. Example: From a student of list, we need to make a map of list of subjects for each student.

List list = new ArrayList<>();
list.add(new Student("Davis", SUBJECT.MATH, 35.0));
list.add(new Student("Davis", SUBJECT.SCIENCE, 12.9));
list.add(new Student("Davis", SUBJECT.GEOGRAPHY, 37.0));
list.add(new Student("Sascha", SUBJECT.ENGLISH, 85.0));
list.add(new Student("Sascha", SUBJECT.MATH, 80.0));
list.add(new Student("Sascha", SUBJECT.SCIENCE, 12.0));
list.add(new Student("Sascha", SUBJECT.LITERATURE, 50.0));

list.add(new Student("Robert", SUBJECT.LITERATURE, 12.0));
Map> map = new HashMap<>(); list.stream().forEach(s -> {
      map.computeIfAbsent(s.getName(), x -> new ArrayList<>()).add(s.getSubject());
      });
System.out.println(map);

Output:

{ Robert=[LITERATURE],
Sascha=[ENGLISH, MATH, SCIENCE, LITERATURE],
Davis=[MATH, SCIENCE, GEOGRAPHY] }

Example: from ArrayList to Map>

List list = new ArrayList<>();
list.add(new Student("Davis", SUBJECT.MATH, 1, 35.0));
list.add(new Student("Davis", SUBJECT.SCIENCE, 2, 12.9));
list.add(new Student("Davis", SUBJECT.MATH, 3, 37.0));
list.add(new Student("Davis", SUBJECT.SCIENCE, 4, 37.0));

list.add(new Student("Sascha", SUBJECT.ENGLISH, 5, 85.0));
list.add(new Student("Sascha", SUBJECT.MATH, 1, 80.0));
list.add(new Student("Sascha", SUBJECT.ENGLISH, 6, 12.0));
list.add(new Student("Sascha", SUBJECT.MATH, 3, 50.0));

list.add(new Student("Robert", SUBJECT.ENGLISH, 5, 12.0));

Map>> map = new HashMap<>();

list.stream().forEach(student -> {
     map.computeIfAbsent(student.getName(), s -> new HashMap<>())
           .computeIfAbsent(student.getSubject(), s -> new   ArrayList<>())
           .add(student.getMarks());
});
System.out.println(map);

Output:

{ Robert={ENGLISH=[12.0]},
Sascha={MATH=[80.0, 50.0], ENGLISH=[85.0, 12.0]},
Davis={MATH=[35.0, 37.0], SCIENCE=[12.9, 37.0]} }

Cheat-Sheet

Goalcode
Collect to a ListCollectors.toList()
Collect to an ArrayList with preallocated sizeCollectors.toCollection(() -> new ArrayList<>(size))
Collect to a SetCollectors.toSet()
Collect to a Set with better iteration performanceCollectors.toCollection(() -> new LinkedHashSet<>())
Collect to a case-insensitive
Set
Collectors.toCollection(() -> new
TreeSet<>(String.CASE_INSENSITIVE_ORDER))
Collect to an EnumSet (best
performance for enums)
Collectors.toCollection(() -> EnumSet.noneOf(AnEnum.class))
Collect to a Map with unique keysCollectors.toMap(keyFunc,valFunc)
Map MyObject.getter() to unique MyObjectCollectors.toMap(MyObject::getter, Function.identity())
Map MyObject.getter() to multiple MyObjectsCollectors.groupingBy(MyObject::getter)

Using Streams to Implement Mathematical Functions

Streams, and especially IntStreams, are an elegant way of implementing summation terms (Σ). The ranges of the Stream can be used as the bounds of the summation.

E.g., Madhava’s approximation of Pi is given by the formula

This can be calculated with an arbitrary precision. E.g., for 101 terms:

double pi = Math.sqrt(12) *
            IntStream.rangeClosed(0, 100)
                     .mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
                     .sum();

Note: With double’s precision, selecting an upper bound of 29 is sufficient to get a result that’s indistinguishable from Math.Pi.

Flatten Streams with flatMap()

A Stream of items that are in turn streamable can be flattened into a single continuous Stream:

Array of List of Items can be converted into a single List.

List<String> list1 = Arrays.asList("one", "two");
     List<String> list2 = Arrays.asList("three","four","five");
     List<String> list3 = Arrays.asList("six");
         List<String> finalList = Stream.of(list1, list2,
list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(finalList);

// [one, two, three, four, five, six]

Map containing List of Items as values can be Flattened to a Combined List

Map> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));

List allValues = map.values() // Collection>
     .stream()                // Stream>
     .flatMap(List::stream)   // Stream
     .collect(Collectors.toList());

System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]

List of Map can be flattened into a single continuous Stream

List> list = new ArrayList<>();
Map map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");

Map map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);

Set<String> output= list.stream()   // Stream>
      .map(Map::values)             // Stream>
      .flatMap(Collection::stream)  // Stream
      .collect(Collectors.toSet()); //Set
// [one, two, three,four]

Parallel Stream

Note: Before deciding which Stream to use please have a look at ParallelStream vs Sequential Stream behavior.

When you want to perform Stream operations concurrently, you could use either of these ways.

List data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream aParallelStream = data.stream().parallel();

Or:

Stream aParallelStream = data.parallelStream();

To execute the operations defined for the parallel stream, call a terminal operator:

aParallelStream.forEach(System.out::println);

(A possible) output from the parallel Stream:

Three
Four
One
Two
Five

The order might change as all the elements are processed in parallel (Which may make it faster). Use parallelStream when ordering does not matter.

Performance impact

In case networking is involved, parallel Streams may degrade the overall performance of an application because all parallel Streams use a common fork-join thread pool for the network.

On the other hand, parallel Streams may significantly improve performance in many other cases, depending of the number of available cores in the running CPU at the moment.

Creating a Stream

All java Collections have stream() and parallelStream() methods from which a Stream can be constructed:

Collection stringList = new ArrayList<>();
Stream stringStream = stringList.parallelStream();

A Stream can be created from an array using one of two methods:

String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream stringStream = Arrays.stream(values);
Stream stringStreamAlternative = Stream.of(values);

The difference between Arrays.stream() and Stream.of() is that Stream.of() has a varargs parameter, so it can be used like:

Stream integerStream = Stream.of(1, 2, 3);

There are also primitive Streams that you can use. For example:

IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

These primitive streams can also be constructed using the Arrays.stream() method:

IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });

It is possible to create a Stream from an array with a specified range.

int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);

Note that any primitive stream can be converted to boxed type stream using the boxed method :

Stream integerStream = intStream.boxed();

This can be useful in some case if you want to collect the data since primitive stream does not have any collect method that takes a Collector as argument.

Reusing intermediate operations of a stream chain

Stream is closed when ever terminal operation is called. Reusing the stream of intermediate operations, when only terminal operation is only varying. we could create a stream supplier to construct a new stream with all intermediate operations already set up.

Supplier> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes",
"melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();

streamSupplier.get().filter(s -> s.startsWith("A")).forEach(System.out::println);
// APPLE

streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);

// BANANA
// BLACKBERRY
// BLUEBERRY

int[] arrays can be converted to List using streams

int[] ints = {1,2,3};
List list = IntStream.of(ints).boxed().collect(Collectors.toList());

Leave a Comment