Java Stream collect
last modified January 27, 2024
Java Stream collect tutorial shows how to do reduction operations using collectors.
Java Stream
Java Stream is a sequence of elements from a source that supports aggregate operations. Streams do not store elements; the elements are computed on demand. Elements are consumed from data sources such as collections, arrays, or I/O resources.
Java Stream collect
Java Stream collect
is a terminal stream operation. It performs a
mutable reduction operation on the elements of the stream. Reduction operations
can be performed either sequentially or in parallel.
Collectors
The Collectors
class contains predefined collectors to perform
common mutable reduction tasks. The collectors accumulate elements into
collections, reduce elements into a single value (such as min, max, count, or
sum), or group elements by a criteria.
Set<String> uniqueVals = vals.collect(Collectors.toSet());
The toSet
method returns a Collector
that accumulates
the input elements into a new Set
.
// can be replaced with min() Optional<Integer> min = vals.stream().collect(Collectors.minBy(Integer::compareTo));
The minBy
returns a Collector
that produces the
minimal element according to a given Comparator
.
Map<Boolean, List<User>> usersByStatus = users().stream().collect(Collectors.groupingBy(User::isSingle));
With groupingBy
we select users who have single status into a group.
Map<Boolean, List<User>> statuses = users().stream().collect(Collectors.partitioningBy(User::isSingle));
With partitioningBy
we separate the users into two groups based
on their status attribute.
Collector
The Collector
interface defines a set of methods which are used
during the reduction process. The following is the interface signature with the
five methods it declares.
public interface Collector<T,A,R> { Supplier<A> supplier(); BiConsumer<A,T> accumulator(); BinaryOperator<A> combiner(); Function<A,R> finisher(); Set<Characteristics> characteristics(); }
A Collector
is specified by four functions that work together to
accumulate entries into a mutable result container, and optionally perform a
final transform on the result.
The T
is the type of elements in the stream to be collected.
The A
the type of the accumulator. The R
is the type
of the result returned by the collector.
The supplier returns a function which creates a new result container. The accumulator returns a function which performs the reduction operation. It accepts two arguments: the first ist the mutable result container (accumulator) and the second the stream element that is folded into the result container.
When the stream is collected in parallel the combiner returns a
function which merges two accumulators. The finisher returns a function
which performs the final transformation from the intermediate result container
to the final result of type R
. The finisher returns an identity
function when the accumulator already represents the final result.
identity
method
of a Function
returns a Function
that always returns
its input arguments.
The characteristics
method returns an immutable set of
Characteristics
which define the behavior of the collector. It can
be used to do some optimizations during the reduction process. For example, if
the set contains CONCURRENT
, then the collection process can be
performed in parallel.
Java Stream collect to list
The Collectors.toList
returns a collector that accumulates the
input elements into a new list.
package com.zetcode; import java.util.List; import java.util.stream.Collectors; public class JavaCollectToListEx { public static void main(String[] args) { var words = List.of("marble", "coin", "forest", "falcon", "sky", "cloud", "eagle", "lion"); // filter all four character words into a list var words4 = words.stream().filter(word -> word.length() == 4) .collect(Collectors.toList()); System.out.println(words4); } }
The example filters a list of strings and transforms the stream into a list. We filter the list to include only strings whose length is equal to four.
var words4 = words.stream().filter(word -> word.length() == 4) .collect(Collectors.toList());
With the stream
method, we create a Java Stream from a list of
strings. On this stream, we apply the filter
method. The
filter
method accepts an anonymous function that returns a boolean
true for all elements of the stream whose length is four. We create a list back
from the stream with the collect
method.
[coin, lion]
These two words have four characters.
Java Stream collect join into string
The Collectors.joining
returns a Collector
that
concatenates the input elements into a string, in encounter order.
package com.zetcode; import java.util.List; import java.util.stream.Collectors; public class JavaCollectJoinEx { public static void main(String[] args) { var words = List.of("marble", "coin", "forest", "falcon", "sky", "cloud", "eagle", "lion"); // can be replaced with String.join var joined = words.stream().collect(Collectors.joining(",")); System.out.printf("Joined string: %s", joined); } }
We have a list of words. We transform the list into a string where the words are separated with comma.
Joined string: marble,coin,forest,falcon,sky,cloud,eagle,lion
Java Stream collect count
The Collectors.counting
retuns a Collector
that counts
the number of elements in the stream.
package com.zetcode; import java.util.List; import java.util.stream.Collectors; public class JavaCollectCountEx { public static void main(String[] args) { var vals = List.of(1, 2, 3, 4, 5); // can be replaced with count var n = vals.stream().collect(Collectors.counting()); System.out.println(n); } }
The example counts the number of elements in the list.
Java Stream collect sum
The Collectors.summintInt
returns a Collector
that
produces the sum of a integer-valued function applied to the input elements.
package com.zetcode; public class Cat { private String name; private int age; public Cat(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { var sb = new StringBuilder("Cat{"); sb.append("name='").append(name).append('\''); sb.append(", age=").append(age); sb.append('}'); return sb.toString(); } }
In the example, we use this Cat
class.
package com.zetcode; import java.util.List; import java.util.stream.Collectors; public class JavaCollectSumAgeEx { public static void main(String[] args) { var cats = List.of( new Cat("Bella", 4), new Cat("Othello", 2), new Cat("Coco", 6) ); // can be replaced with mapToInt().sum() var ageSum = cats.stream().collect(Collectors.summingInt(cat -> cat.getAge())); System.out.printf("Sum of cat ages: %d%n", ageSum); } }
The example sums the age of the cats.
var ageSum = cats.stream().collect(Collectors.summingInt(cat -> cat.getAge()));
The parameter of the summingInt
method is a mapper function which
extracts the property to be summed.
Java Stream collectingAndThen
The Collectors.collectingAndThen
adapts a Collector
to
perform an additional finishing transformation.
package com.zetcode; import java.text.NumberFormat; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; public class JavaCollectAndThenEx { public static void main(String[] args) { var vals = List.of(230, 210, 120, 250, 300); var avgPrice = vals.stream().collect(Collectors.collectingAndThen( Collectors.averagingInt(Integer::intValue), avg -> { var nf = NumberFormat.getCurrencyInstance(new Locale("en", "US")); return nf.format(avg); }) ); System.out.printf("The average price is %s%n", avgPrice); } }
The example calculates an average price and then formats it.
Java Stream custom collector
The Collector.of
returns a new Collector
described by
the given supplier, accumulator, combiner, and finisher functions.
In the following example, we create a custom collector.
package com.zetcode; import java.util.List; import java.util.StringJoiner; import java.util.stream.Collector; public class CustomCollector { public static void main(String[] args) { List<User> persons = List.of( new User("Robert", 28), new User("Peter", 37), new User("Lucy", 23), new User("David", 28)); Collector<User, StringJoiner, String> personNameCollector = Collector.of( () -> new StringJoiner(" | "), // supplier (j, p) -> j.add(p.getName()), // accumulator (j1, j2) -> j1.merge(j2), // combiner StringJoiner::toString); // finisher String names = persons .stream() .collect(personNameCollector); System.out.println(names); } } class User { private String name; private int age; User(String name, int age) { this.name = name; this.age = age; } public String getName() { return this.name; } @Override public String toString() { return String.format("%s is %d years old", name, age); } }
In the example, we collect the names from the list of user objects.
Collector<User, StringJoiner, String> personNameCollector = ...
The Collector<User, StringJoiner, String>
has three types.
The first is the type of input elements for the new collector. The second
is the type for the intermediate result and the third for the final result.
Collector.of( () -> new StringJoiner(" | "), // supplier (j, p) -> j.add(p.getName()), // accumulator (j1, j2) -> j1.merge(j2), // combiner StringJoiner::toString); // finisher
We create our custom collector. First, we build an initial result container. In
our case it is a StringJoiner
. The accumulator simply adds the name
from the current user object to the StringJoiner
. The combiner
merges two partial results in case of parallel processing. Finally, the finisher
turns the StringJoiner
into a plain string.
Robert | Peter | Lucy | David
Java Stream collect group by
With the Collectors.groupingBy
method we can separate the stream
elements into groups based on the specified criterion.
package com.zetcode; import java.math.BigDecimal; public class Product { private String name; private String category; private BigDecimal price; public Product(String name, String category, BigDecimal price) { this.name = name; this.category = category; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } @Override public String toString() { var sb = new StringBuilder("Product{"); sb.append("name='").append(name).append('\''); sb.append(", category='").append(category).append('\''); sb.append(", price=").append(price); sb.append('}'); return sb.toString(); } }
We will group Product
objects.
package com.zetcode; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class JavaCollectGroupByCategory { public static void main(String[] args) { Map<String, List<Product>> productsByCategories = products().stream().collect( Collectors.groupingBy(Product::getCategory)); productsByCategories.forEach((k, v) -> { System.out.println(k); for (var name : v) { System.out.println(name); } }); } private static List<Product> products() { return List.of( new Product("apple", "fruit", new BigDecimal("4.50")), new Product("banana", "fruit", new BigDecimal("3.76")), new Product("carrot", "vegetables", new BigDecimal("2.98")), new Product("potato", "vegetables", new BigDecimal("0.92")), new Product("garlic", "vegetables", new BigDecimal("1.32")), new Product("ginger", "vegetables", new BigDecimal("2.45")), new Product("white bread", "bakery", new BigDecimal("1.50")), new Product("roll", "bakery", new BigDecimal("0.08")), new Product("bagel", "bakery", new BigDecimal("0.15")) ); } }
We have a list of products. With the Collectors.groupingBy
,
we separate the products into groups based on their category.
bakery Product{name='white bread', category='bakery', price=1.50} Product{name='roll', category='bakery', price=0.08} Product{name='bagel', category='bakery', price=0.15} fruit Product{name='apple', category='fruit', price=4.50} Product{name='banana', category='fruit', price=3.76} vegetables Product{name='carrot', category='vegetables', price=2.98} Product{name='potato', category='vegetables', price=0.92} Product{name='garlic', category='vegetables', price=1.32} Product{name='ginger', category='vegetables', price=2.45}
Java Stream collect partition by
Partitioning is a special case of grouping. Partitioning operation divides the stream into two groups based on the given predicate function.
package com.zetcode; public class User { private String name; private boolean single; public User(String name, boolean single) { this.name = name; this.single = single; } public boolean isSingle() { return single; } @Override public String toString() { var sb = new StringBuilder("User{"); sb.append("name='").append(name).append('\''); sb.append(", single=").append(single); sb.append('}'); return sb.toString(); } }
We will group user objects.
package com.zetcode; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class JavaCollectPartitionByEx { public static void main(String[] args) { Map<Boolean, List<User>> statuses = users().stream().collect( Collectors.partitioningBy(User::isSingle)); statuses.forEach((k, v) -> { if (k) { System.out.println("Single: "); } else { System.out.println("In a relationship:"); } v.forEach(System.out::println); }); } private static List<User> users() { return List.of( new User("Julia", false), new User("Jake", false), new User("Mike", false), new User("Robert", true), new User("Maria", false), new User("Peter", true) ); } }
In the example, we partition the stream into two groups based on the single attribute.
Map<Boolean, List<User>> statuses = users().stream().collect(Collectors.partitioningBy(User::isSingle));
The Collectors.partitioningBy
takes the isSingle
predicate, which returns a boolean value indicating the status of the user.
In a relationship: User{name='Julia', single=false} User{name='Jake', single=false} User{name='Mike', single=false} User{name='Maria', single=false} Single: User{name='Robert', single=true} User{name='Peter', single=true}
Source
In this article we have have worked with Java Stream predefined and custom collectors.
Author
List all Java tutorials.