Commonly Used Terminal Operations In Java Streams

  • Post last modified:August 22, 2022
  • Reading time:6 mins read

Introduction

  • In Java Streams we can perform two type of operation , once is terminal operations such max, count, reduce and other is intermediate Operations such map, filter etc.
  • Basically Intermediate operation perform some logic and transform stream elements. For exam filter element will filter from source based on defined filter logic, while map operation map element from one type to another.
  • Whereas , Terminal operation perform termination of stream with eventual operation. This operation can be find max of stream elements, or converting the stream of element to list or map.
  • Major objective of this article is to go through mostly used Stream terminal operation in Java along with examples.

Terminal Operations

All Match

  • This operation provide the terminal result based on passed predicate. for example , if we want to check if all the customers in our list are greater than 18 years old then we can perform allMatch at the end of stream pipeline.
  • This short-circuiting operation that means any of the element doesn’t satisfy the condition then it immediate return the result without processing subsequent elements.
boolean gt18 = customers.stream().allMatch(customer -> customer.getAge() > 18);
System.out.println(gt18);

boolean gt20 = customers.stream().allMatch(customer -> customer.getAge() > 20);
System.out.println(gt20);

Count

  • Count is another popular terminal operation on streams that helps us counting the total number in our streams.
System.out.println("premium customer count "+customers.stream()
                .filter(Ops::premiumCustomer)
                .count());

AnyMatch

  • This terminal operations returns the result based on any elements matches with predicate .
  • For example, if any of our customer is greater than age 60 then our output would true other false.
System.out.println(customers.stream()
                .anyMatch(customer -> customer.getAge()>60));

FindAny

  • If you apply parallel stream then the result is not deterministic. findAny might result different result.
  • In Below example i ran anyMatch twice and results were different.
System.out.println("----");
Optional<Customer> any = customers.stream().parallel().filter(customer -> customer.getAge() >= 30).findAny();
System.out.println(any.get().toString());

Optional<Customer> any1 = customers.stream().parallel().filter(customer -> customer.getAge() >= 30).findAny();
        System.out.println(any1.get().toString());
  • So if we need deterministic result then we might want to use findFirst.

FindFirst

  • Find first returns the first element in the stream. This method respects the encounter order if it has one otherwise returns any element.
System.out.println(customers.stream()
                .filter(customer -> customer.isPremium())
                .findFirst());

Max

  • This operation returns the max of the provided stream of integers. It accepts Comparator that basically defines comparison logic for the provided elements. 
List<Integer> integers = List.of(1, 2, 3, 4,100,34);
Optional<Integer> max = integers.stream().max((a, b) -> a - b);
System.out.println(max.get());
  • For of list of customers , we can find customer with max age.
System.out.println("customer age : "+customers.stream()
                .max((c1,c2)->c1.getAge()- c2.getAge()));

Min

  • Like above operation this terminal operation takes comparator as argument and returns min element based on comparator logic.
 System.out.println("customer min age : "+customers.stream()
                .min((c1,c2)->c1.getAge()- c2.getAge()));

ToArray

  • ToArray method takes IntFunction as argument and convert streams to Array. In below example we are creating customer array from stream of array.
Customer[] customers1 = customers.stream().toArray(size -> new Customer[size]);
Customer[] customers2 = customers.stream().toArray(Customer[]::new);// alternateway

Reduce

  • There are three variant of reduce operation as shown below.

reduce(BinaryOperator<T> accumulator)

  • This reduce operation perform reduction operation based on accumulator passed as parameter.
  • In below example, we are passing BinaryOperator takes two argument and returns the accumulator result. We are calculating sum of two elements and returning the result.
BinaryOperator<Integer> binaryOperator = new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer a1, Integer a2) {
                return a1+a2;
            }
        };

Optional<Integer> reduce = List.of(1, 2, 3, 4).stream().reduce(binaryOperator);
System.out.println("sum : "+reduce.get());

reduce(U identity, BinaryOperator<T> accumulator)

  • This reduce operation does the same Job as above mentioned but we can also define Identity that basically provides initial value of reduce operation.
  • In below example , we initialize our first reduce with 10 , which basically apply to the result of accumulator. In our accumulator we are multiplying two elements of stream, so the result is 1*2*3*4 = 24 , and at finally our identity is multiplied to the result which makes the result to 240.
  • Similarly, in next reduce operation our identity is 1 , hence the result is 24.
 BinaryOperator<Integer> binaryOperator = new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer a1, Integer a2) {
//                System.out.println("======");
//                System.out.println(a1+"*"+a2);
                return a1*a2;
            }
};
Integer reduceSeq = List.of(1, 2, 3, 4).stream().reduce(10,  binaryOperator);
System.out.println("reduceSeq : "+reduceSeq);
Integer reduceSeq1 = List.of(1, 2, 3, 4).stream().reduce(1,  binaryOperator);
System.out.println("reduceSeq1 : "+reduceSeq1);

reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<T> combiner)

  • In this reduce operation, we have Identity , accumulator and combiner. this reduce operation makes more sense for parallel stream . In parallel stream , it’s split the elements in multiple threads so it the identity is applied to each thread. Then accumulator operation is applied to the elements and combiner combines the result of each thread. 
BinaryOperator<Integer> binaryOperator = new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer a1, Integer a2) {
//                System.out.println("======");
//                System.out.println(a1+"*"+a2);
                return a1*a2;
            }
        };
        
BiFunction<Integer, Integer, Integer> biFunction = new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer o1, Integer o2) {
                System.out.println(o1+"*"+o2);
                return o1*o2;
            }
 };
 
Integer reduce3 = List.of(1, 2, 3, 4).parallelStream().reduce(1, biFunction, binaryOperator);
System.out.println("intValue: "+reduce3.intValue());

Collect

  • Collect operation takes the stream of values and accumulate it into Collector of Collection type such as list, set etc.
List<Customer> collect = customers.stream().collect(Collectors.toList());
  • We also use static methods from Collectors class such as GroupBy that returns map based on grouping key. In Below example we are grouping our customer based on true or false for premium.
Map<Boolean, List<Customer>> collect1 = customers.stream().collect(Collectors.groupingBy(Customer::isPremium));
collect1.entrySet().stream().forEach(System.out::println);
  • Collectors provide many useful static methods such as average, counting , flatMapping and so on.

ForEach

  • This method performs defined action on each element of stream.
  • For example in the below code , we are performing mail send action on each customer who are greater than 30 years old and they have premium membership.
customers.stream().forEach(a->{
            if(a.getAge()>30 && a.isPremium){
                // mail send code or something
            }
});

Conclusion

  • In this article we , we discussed commonly Streams Terminal Operations in Java.
  • This article just touches the surface and doesn’t cover all the aspects of the mentioned operation so please check the documentation for Streams interface.

Leave a Reply