ZetCode

Java Stream reduce

last modified July 8, 2024

In this article we show to perform reduction operations on Java streams.

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.

Stream reduction

A reduction is a terminal operation that aggregates a stream into a type or a primitive. The Java Stream API contains a set of predefined reduction operations, such as average, sum, min, max, and count, which return one value by combining the elements of a stream.

The reduce method

Stream.reduce is a general-purpose method for generating our custom reduction operations.

Optional<T> reduce(BinaryOperator<T> accumulator)

This method performs a reduction on the elements of this stream, using an associative accumulation function. It returns an Optional describing the reduced value, if any.

T reduce(T identity, BinaryOperator<T> accumulator)

This method takes two parameters: the identity and the accumulator. The identity element is both the initial value of the reduction and the default result if there are no elements in the stream. The accumulator function takes two parameters: a partial result of the reduction and the next element of the stream. It returns a new partial result. The Stream.reduce method returns the result of the reduction.

Built-in reductions

The following example uses two predefined reduction operations.

Main.java
import java.util.Arrays;

void main() {

    int vals[] = { 2, 4, 6, 8, 10, 12, 14, 16 };

    int sum = Arrays.stream(vals).sum();
    System.out.printf("The sum of values: %d%n", sum);

    long n = Arrays.stream(vals).count();
    System.out.printf("The number of values: %d%n", n);
}

We have an array of integers. We create a stream from the array with Arrays.stream and perform two reductions: sum and count.

The sum of values: 72
The number of values: 8

Java reduce Optional

The reduce method with one parameter returns an Optional, which is a Java class for null safety.

Main.java
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

void main() {

    List<Car> persons = Arrays.asList(new Car("Skoda", 18544),
            new Car("Volvo", 22344),
            new Car("Fiat", 23650),
            new Car("Renault", 19700));

    Optional<Car> car = persons.stream().reduce((c1, c2)
            -> c1.price() > c2.price() ? c1 : c2);

    car.ifPresent(System.out::println);
}

record Car(String name, int price) {
}

The example creates a list of car objects. We compute the most expensive car.

Optional<Car> car = persons.stream().reduce((c1, c2)
        -> c1.price() > c2.price() ? c1 : c2);

From the list, we create a stream; the accumulator of the reduce method compares the prices of the cars and returns the more expensive one.

car.ifPresent(System.out::println);

If the returned reduction value is not null, we print it to the console.

Car{name=Fiat, price=23650}

The next example adds other use cases.

Main.java
import java.util.stream.IntStream;

void main() {

    IntStream.range(1, 10).reduce((x, y) -> x + y)
            .ifPresent(System.out::println);
    IntStream.range(1, 10).reduce(Integer::sum)
            .ifPresent(System.out::println);
    IntStream.range(1, 10).reduce(MyUtil::add2Ints)
            .ifPresent(System.out::println);
}

class MyUtil {

    static int add2Ints(int num1, int num2) {
        return num1 + num2;
    }
}

We create three different accumulator functions to compute the sum of 1..10 values.

IntStream.range(1, 10).reduce((x, y) -> x + y)
        .ifPresent(System.out::println);

In the first case, we have a lambda expression doing the addition.

IntStream.range(1, 10).reduce(Integer::sum)
        .ifPresent(System.out::println);

The second case uses a built in Integer::sum method.

IntStream.range(1, 10).reduce(MyUtil::add2Ints)
        .ifPresent(System.out::println);

Finally, we have a custom addition method.

Java reduce with identity

As we have already mentioned, the identity is both the initial value of the reduction and the default result if there are no elements in the stream.

Main.java
import java.time.LocalDate;
import java.time.chrono.IsoChronology;
import java.util.ArrayList;
import java.util.List;

void main() {

    List<User> users = new ArrayList<>();

    users.add(new User("Frank", LocalDate.of(1979, 11, 23)));
    users.add(new User("Peter", LocalDate.of(1985, 1, 18)));
    users.add(new User("Lucy", LocalDate.of(2002, 5, 14)));
    users.add(new User("Albert", LocalDate.of(1996, 8, 30)));
    users.add(new User("Frank", LocalDate.of(1967, 10, 6)));

    int maxAge = users.stream().mapToInt(User::getAge).reduce(0, Math::max);

    System.out.printf("The oldest user's age: %s%n", maxAge);
}


record User(String name, LocalDate dateOfBirth) {
    public int getAge() {
        return dateOfBirth.until(IsoChronology.INSTANCE.dateNow()).getYears();
    }
}

In the example, we create a list of users. The example calculates the age of the oldest user.

int maxAge = users.stream().mapToInt(User::getAge).reduce(0, Math::max);

From the list we create a Java stream. The stream is mapped to an IntStream with a mapToInt method. Finally, the reduce method provides an identity value (0) and an accumulator; the accumulator compares the age values and returns the bigger one.

Source

Java Stream documentation

In this article we have have worked with Java Stream reduction operations.

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Java tutorials.