Java Streams

Methods in Stream API | filter, map, reduce

March 14, 2020

Functional programming languages typically provide several useful higher-order functions. (a function that takes one or more functions or returns a function or does both is a higher-order function).

The most common Higher-order functions are Filter, Map and Reduce.

filter method

As the name suggests, the filter method filters the elements based upon the condition we give to it. You can think of filter() as the functional equivalent of an if statement!

filter method takes the predicate as an argument and returns a stream of elements that matches the condition of the predicate.

Stream<T> filter(Predicate<? super T> predicate);

Predicate Functional Interface has the method test, which takes something and returns a Boolean after executing the given condition (that we provide using lambda).

import java.util.stream.Stream;

//Program to filter the even elements from stream
public class FilterOperation {

	public static void main(String[] args) {
//Stream.of method creates the stream of the elements passed to it.
	Stream.of(1,2,3,4,98,56,90,23,56)
//filter takes prdicate, returns a stream of elements that matches the condition
		.filter(val -> val % 2 == 0)
		.forEach(val -> System.out.println(val));
	}
}
Output:
2
4
98
56
90
56

map method

The map() method is the method of Stream class, which takes a mapper function as an argument. It is an intermediate operation, which returns a stream that gets generated by applying the given function to the elements of this stream.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

In other words, map function takes each stream element and map it to some other value returned by the mapper function. For example,

Example 01: Multiplication table of five:

  • Stream map() function with the operation of number * 5 on each element of the stream.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapOperation {
	
	public static void main(String[] args) {
            //each element will be mapped to a new element e*5
	    List<Integer> TableOfFive = Stream.of(1,2,3,4,5,6,7,8,9,10)
		    .map(e -> e*5)
		    .collect(Collectors.toList());
            //displaying stream
            TableOfFive.forEach(System.out::println);
        }
}
//Output
5
10
15
20
25
30
35
40
45
50

Here the map function is mapping each stream element to some other value of the same type, which is int. Consider the following example where stream element is mapped to the value of different type.

Example 02:

//This is the bean class for Books
public class Book {
	
	//here we have

	private String name;
	private String Author;
	private String genre;
	private double rating;
	
	// a constructor
	
	public Book(String name, String Author, String genre, double rating) {
		this.name = name;
		this.Author = Author;
		this.genre = genre;
		this.rating = rating;
	}

	// getters and setters for these 4 fields
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAuthor() {
		return Author;
	}
	public void setAuthor(String author) {
		Author = author;
	}
	public String getGenre() {
		return genre;
	}
	public void setGenre(String genre) {
		this.genre = genre;
	}
	public double getRating() {
		return rating;
	}
	public void setRating(double rating) {
		this.rating = rating;
	}

        //toString method
        @Override
	public String toString() {
		return "Book [name=" + name + ", Author=" + Author + ", genre=" + genre + ", rating=" + rating + "]";
	}
}
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapOperation {
	
	public static void main(String[] args) {

    	     List<Book> books = new ArrayList<>();

		books.add(new Book("The Alchemist", "Paulo Coelho", "Adventure", 4.408789797));
		books.add(new Book("The Notebook", "Nicholas Sparks", "Romance", 4.10));
		books.add(new Book("Horror Cocktail", "Robert Bloch", "Horror", 2.67));
		books.add(new Book("House of Leaves", "Mark Z. Danielewski", "Horror", 4.10908908));
			
		//To get only the names of the book 
		
		books.stream()    
		.filter((book) -> book.getGenre().equalsIgnoreCase("Horror"))
		.filter((book) -> book.getRating() > 3)
		.map(obj -> obj.getName())
		//print the names.
		.forEach(System.out :: println);       
        }
}

Here map function is mapping every stream element of object type to string type of element.

Note: We should not try to change the state of an object by using map function because in that case, we might get ConcurrentModificationException at run time.

reduce method

Many times, we need to perform operations where a stream reduces to single resultant value like sum, product, maximum, minimum, etc. For that Stream API has reduce method.

Reducing is the repeated process of combining all elements. Reduce operation takes two arguments: identity value and BinaryOperator.

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

  • Identity value is the value that has no effect when used in the operation. Like 0 for sum operation, 1 for multiplication, etc. This will be the first argument for BinaryOperator initially.
  • BinaryOperator takes two arguments of similar type and returns the result of the same type.

Consider the following example,

import java.util.stream.Stream;

public class ReduceOperation {

	public static void main(String[] args) {

		// reduce operation on a stream, with identity operator 0, and BinaryOperator to add elements.
		Integer sum = Stream.of(1,2,3,4,5,6,7,8,9,10)
		.reduce(0,(e1,e2) -> e1+e2); 
		//printing the sum
		System.out.println("Sum is: "+ sum);
	}
}
//Output
Sum is: 55

Explanation: Reduce operation applies the given binary operator to each element in the stream. where one argument to the operator is the return value of the previous application and another is the current stream element.

Stream.reduce() in java

One argument to this BinaryOperator is the accumulator which is initially the identity value (0 for sum), and another will be the current stream element (1). Then this returns the sum of both values (which will be 1).

Now, this returned value becomes the accumulator for the second step. [ 1, (1,2) -> 1+2 ]

And at the last step, the value returned by the binary operator will be returned by reduce method and get assigned to sum.