Java Streams

Streams are not containers of data or collection of objects

December 7, 2019

Some of the developers are sometimes getting confuse stream with collections. Stream is not collection and does not contain any data.

Stream is indeed a fancy iterator which just takes the data from a source and process that only once. Stream does not hold any data but processes the elements one by one. Just like fluid is being passed through a pipe.

Let’s just try to prove this with a code Example.

In our previous blog, we used a Book bean class having properties like name, author, genres, and rating. Let’s continue with the same Example. Create a Book Bean Class first.

public class Book {
	
	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;
	}
	// and 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;
	}
	@Override
	public String toString() {
		return "Book[ name=" + name + ", Author=" + Author + ", genre=" + genre + ", rating=" + rating + "]";
	}
}

Let’s do walk through this code to prove the point; Stream is not containers but just a one time flow of elements.

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamNotContainers {
 public static void main(String [] args) {
   // Step01 : Let's take a List of Books	
   List < Book > books = new ArrayList < > ();
   // Step02 : Add Some books here to operate upon
   books.add(new Book("The Alchemist", "Paulo Coelho", "Adventure", 4.40)); 
   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.10));
   
   // Step:03 Let's now filter the books by genres and Rating > 3
   // Here we are operating on books stream
   
   List <Book> popularHorrorBookss = books.stream()
   .filter((book) -> book.getGenre().equalsIgnoreCase("Horror"))
   .filter((book) -> book.getRating() > 3)
   .collect(Collectors.toList());

   // Step:04 Let's Print these books 
   
   popularHorrorBookss.forEach(book -> System.out.println(book));

   // Step:05 Let's Now again anoter stream to find only Romantic books
   // Perfectly fine untill now
   
   List < Book > popularRomanticBookss = books.stream()
   .filter((book) -> book.getGenre().equalsIgnoreCase("Romance"))
   .filter((book) -> book.getRating() > 3)
   .collect(Collectors.toList()); 
   
   popularRomanticBookss.forEach(book -> System.out.println(book));
   
   // Step:06 let's check if we can find first Horror and then Romantics books from creating only one stream
   
   Stream<Book> stream = books.stream();
   
   stream.filter((book) -> book.getGenre().equalsIgnoreCase("Horror"))
   .filter((book) -> book.getRating() > 3)
   .collect(Collectors.toList()); 
   
   //Step 07 refer the same Stream again to filter 
   
   stream.filter((book) -> book.getGenre().equalsIgnoreCase("Romance"))
   .filter((book) -> book.getRating() > 3)
   .collect(Collectors.toList()); 
   
   // Look What we get.
   
 }
}

NOTE: First, Comment step 06,07 from the code and run.

In Step 01 we are having a List of Books, In Step 02 we have added some books to the books object of a list.

Step 03 we have created a Stream out of books List collection and filtered that

First with genres and then ratings, finally collecting them back to another list object popularHorrorBookss and printing them using for each of List.

Same way in Step o5 we are filtering the books rating > 3 and genres= Romance.

Until now everything is fine.

Now, Let’s try to use the same stream for filtering 2 times first Hoor books and then Romantics books.

Step 06 let’s create s Stream first from the books and store the reference to books. using books let’s first filter the Horror Books.

In Step 07 let’s use the same reference and filter the Romantics books.

See What Happens, here comes the Exception

Book [name=The Notebook, Author=Nicholas Sparks, genre=Romance, rating=4.1]
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.base/java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
	at java.base/java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)
	at java.base/java.util.stream.ReferencePipeline$StatelessOp.<init>(ReferencePipeline.java:629)
	at java.base/java.util.stream.ReferencePipeline$2.<init>(ReferencePipeline.java:165)
	at java.base/java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:164)
	at StreamNotContainers.main(StreamNotContainers.java:48)

As the Exception itself suggest – java.lang.IllegalStateException: stream has already been operated upon or closed

Streams are like the pipeline, Through which data will flow only once and after that stream will be disposed of. Stream can’t be used again.

This proves our point that Streams are not data containers but a fancy iterator or a pipeline.