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
Goal | code |
Collect to a List | Collectors.toList() |
Collect to an ArrayList with preallocated size | Collectors.toCollection(() -> new ArrayList<>(size)) |
Collect to a Set | Collectors.toSet() |
Collect to a Set with better iteration performance | Collectors.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 keys | Collectors.toMap(keyFunc,valFunc) |
Map MyObject.getter() to unique MyObject | Collectors.toMap(MyObject::getter, Function.identity()) |
Map MyObject.getter() to multiple MyObjects | Collectors.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());