Java’s Stream API

Akanksha
4 min readNov 25, 2023

Streams were introduced in Java 8 as a part of the standard java utility package — java.util.stream

Java 8’s stream API

A Stream of Data

Conceptually , an object of type Stream represents a sequential stream of data, which once consumed, cannot be recovered. It is an implementation of the Internal Iterator Design Pattern in the java utilities, i.e, while using a stream, we will have no control over the flow of data in it. A streams’ consumablity makes it a preferred choice over collections whenever immutability is required. Data in streams is lazily loaded, i.e, an intermediate operation would not be evaluated unless a terminal operation has to be performed [we will talk about this later].

Analogies can be drawn with a physical stream, say a stream of water for that matter. You don’t control its flow, and once it is consumed, it cannot be recovered.

Creating a Stream

To create an object of type Stream we can call the stream() method on any collection object

List<String> words = Arrays.asList("Akanksha", "has", "moved", "to", "Hyderabad", "from", "Dehradun");
Stream<String> wordStream = words.stream();

or we can also call the of(T... values) from the stream interface

Stream<String> wordStream = Stream.of("Akanksha", "has", "moved", "to", "Hyderabad", "from", "Dehradun");

Stream Operations and Pipeline

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines

Any function invoked from a stream that also returns a stream is called an intermediate function, for example

  • map(Function )
  • filter(Predicate )
  • sorted(Comparator )
  • distinct()

Owing to the forth mentioned fact, such functions can also be chained using the dot operator.

Functions that are invoked using a stream but does not return one are called terminal functions, for example

  • collect(Collector )
  • forEach(Consumer )
  • count()
  • reduce(Object , BinaryOperator )

These function are placed at the end of a stream pipeline.

A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

Map, Filter and Reduce

// declaration of the map fucntion in the Stream interface    
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

// declaration of the filter fucntion in the Stream interface
Stream<T> filter(Predicate<? super T> predicate);

// declaration of reduce fucntion in the Stream interface
Optional<T> reduce(Object identity, BinaryOperator<T> accumulator);

Maps

The map function is used for mapping the data in a stream to some other data, which is also contained in a stream. It receives an instance of type Function, (or a lambda expression) and returns a stream of the same type as the return type of the function. Each object in the invoking stream is mapped using the function. Example usage of map is shown below

List<String> words = Arrays.asList("Akanksha", "has", "moved", "to", "Hyderabad", "from", "Dehradun");

//example - mapping each word to its length
Stream<String> wordStream1 = words.stream();
Stream<Integer> wordLength = wordStream1.map(String::length);
wordLength.forEach(System.out::println);

/* Output:
* 8
* 3
* 5
* 2
* 9
* 4
* 8
*/

//example - mapping each word to upper case
Stream<String> wordStream2 = words.stream();
Stream<String> wordUpperCase = wordStream2.map(String::toUpperCase);
wordUpperCase.forEach(System.out::println);
/* Output:
* AKANKSHA
* HAS
* MOVED
* TO
* HYDERABAD
* FROM
* DEHRADUN
*/
*/

Filter

Filter accepts a predicate function (i.e. a function that returns a boolean value) and return a stream of objects for which the the function is true.

//example - filtering words of length greater than 4
Stream<String> wordStream1 = words.stream();
Stream<String> biggerWords = wordStream1.filter(word -> word.length() > 4);
biggerWords.forEach(System.out::println);
/* Output:
* Akanksha
* moved
* Hyderabad
* Dehradun
*/

//example - filtering words starting with capital letter
Stream<String> wordStream2 = words.stream();
Stream<String> propperNouns = wordStream2.filter(word -> Character.isUpperCase(word.charAt(0)));
propperNouns.forEach(System.out::println);
/* Output:
* Akanksha
* Hyderabad
* Dehradun
*/

Reduce

The reduce function is used to reduce a stream to some non-stream object or value. It can be read as reduce(identityObject, (runningObject, currentObject) -> {return nextRunningObject}) . All the Object must be of the same type as the data in the invoking Stream.

List<String> words = Arrays.asList("Akanksha", "has", "moved", "to", "Hyderabad", "from", "Dehradun");

//example - joining all words into a sentence
Stream<String> wordStream1 = words.stream();
String sentence = wordStream1.reduce("", (s, word) -> s + " " + word);
System.out.println(sentence);

/*Output:
* Akanksha has moved to Hyderabad from Dehradun
*/

Pipeline Examples

For the following examples, we are going to use movieStream

import java.util.stream.*;

class Movie {
String name;
int rating;

public Movie(String name, int rating) {
this.name = name;
this.rating = rating;
}
}

public class Main {
public static void main(String[] args) {
Stream<Movie> movieStream = Stream.of(
new Movie("The Godfather", 9),
new Movie("The Shawshank Redemption", 9),
new Movie("Schindler's List", 8),
new Movie("Forrest Gump", 8),
new Movie("The Dark Knight", 9),
new Movie("The Lord of the Rings", 9),
new Movie("Fight Club", 8),
new Movie("Star Wars", 8),
new Movie("The Matrix", 8),
new Movie("Goodfellas", 8)
);
}
}

Example — Map all the names of the movies rated 9 and above to ‘name 🌟'

movieStream
.filter((movie) -> movie.rating >= 9)
.map((movie) -> movie.name + "🌟")
.forEach(System.out::println);

Example — get the sum of all the movie ratings

int sum = movieStream
.map(movie -> movie.rating)
.reduce(0, (a, b) -> a + b)

Example — save all the movies beginning in ‘The’ into a list

List<Movie> movies = movieStream
.filter(movie -> movie.name.startsWith("The"))
.collect(Collectors.toList());

Example — Print the number of movies with rating 8

System.out.println(
movieStream
.filter(movie -> movie.rating == 8)
.count()
);

Example — get a list of movies sorted by ratings

List<Movie> movies = movieStream.sorted((a, b) -> Integer.compare(a.rating, b.rating)).collect(Collectors.toList());

--

--