Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Collectors

The terminal operation collect gathers the elements of a stream into a collection, a string or a single number. What you get is decided by a collector — an object that describes how to gather the elements. Ready-made collectors live in the Collectors class.

collect and toList

The simplest collector gathers elements into a list:

void main() {
    List<String> cities = Stream.of("Rome", "Oslo", "Lima")
            .collect(Collectors.toList());
 
    IO.println(cities);
}

Output:

[Rome, Oslo, Lima]

Since Java 16 a stream also has the short toList() method. The difference is that toList() returns an unmodifiable list, while Collectors.toList() returns an ordinary ArrayList you can add to. New code usually uses toList().

A set and a string

Collectors.toSet() gathers elements into a set and so removes duplicates. Collectors.joining glues strings together with a separator and can also add a prefix and a suffix:

void main() {
    List<String> tags = List.of("java", "code", "java", "lesson");
 
    Set<String> unique = tags.stream().collect(Collectors.toSet());
    IO.println(unique.size());
 
    String line = tags.stream().distinct().collect(Collectors.joining(", ", "[", "]"));
    IO.println(line);
}

Output:

3
[java, code, lesson]

The order of elements in a HashSet is not guaranteed, so only the size of the set is printed here.

toMap

Collectors.toMap builds a map. You pass it two functions: one gets the key from an element, the other gets the value:

record Product(String name, int price) {}
 
void main() {
    List<Product> products = List.of(new Product("Tea", 5), new Product("Coffee", 9));
 
    Map<String, Integer> prices = products.stream()
            .collect(Collectors.toMap(Product::name, Product::price));
 
    IO.println(prices.get("Coffee"));
}

Output:

9

Totals

Some collectors compute a total right away: counting() gives the count, summingInt the sum, averagingDouble the average:

record Order(String customer, double amount) {}
 
void main() {
    List<Order> orders = List.of(
            new Order("Anna", 20.0),
            new Order("Oleg", 35.5),
            new Order("Anna", 12.5));
 
    long count = orders.stream().collect(Collectors.counting());
    double avg = orders.stream().collect(Collectors.averagingDouble(Order::amount));
 
    IO.println("Orders: " + count);
    IO.println("Average order: €" + avg);
}

Output:

Orders: 3
Average order: €22.666666666666668

On their own these collectors look longer than count() or average(). Their real strength shows in grouping — the topic of the next lesson.

Unmodifiable collections

If the result doesn't need to change, use Collectors.toUnmodifiableList(), toUnmodifiableSet() or toUnmodifiableMap(). Trying to modify such a collection throws UnsupportedOperationException, which protects the data from accidental damage.

Common mistake

toMap fails when two elements produce the same key:

record Order(String customer, double amount) {}
 
void main() {
    List<Order> orders = List.of(new Order("Anna", 20.0), new Order("Anna", 12.5));
 
    Map<String, Double> byCustomer = orders.stream()
            .collect(Collectors.toMap(Order::customer, Order::amount));
}

Output:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key Anna (attempted merging values 20.0 and 12.5)
	at Main.main(Main.java:7)

If duplicates are possible, pass a merge function as the third argument — it decides what to do with two values for the same key. For example, Double::sum adds the order amounts: Collectors.toMap(Order::customer, Order::amount, Double::sum).

Thanks for reading!

Donate to the site — we work hard for you.