Java 8 Stream

A Stream represents a sequence of objects from a source, which supports aggregate operations.

Following are the characteristics of a Stream −

Sequence of elements − A stream provides a set of elements of specific type in a sequential manner. A stream gets/computes elements on demand. It never stores the elements.

Source − Stream takes Collections, Arrays, or I/O resources as input source.

Aggregate operations − Stream supports aggregate operations like filter, map, limit, reduce, find, match, and so on.

Pipelining − Most of the stream operations return stream itself so that their result can be pipelined. These operations are called intermediate operations and their function is to take input, process them, and return output to the target. collect() method is a terminal operation which is normally present at the end of the pipelining operation to mark the end of the stream.

Automatic iterations − Stream operations do the iterations internally over the source elements provided, in contrast to Collections where explicit iteration is required.

Generating Streams-

With Java 8, Collection interface has two methods to generate a Stream.

stream() − Returns a sequential stream considering collection as its source.

parallelStream() − Returns a parallel Stream considering collection as its source.

Ex-

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

Various methods in Stream Interface:-

forEach()

Stream has provided a new method ‘forEach’ to iterate each element of the stream. The following code segment shows how to print 10 random numbers using forEach.

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

map()

The ‘map’ method is used to map each element to its corresponding result. The following code segment prints squares of numbers using map.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
//get list of squares
List<Integer> squaresList = numbers.stream().map( i -> i*i).collect(Collectors.toList());

filter()

The ‘filter’ method is used to eliminate elements based on a criteria. The following code segment prints a count of empty strings using filter.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
int count = strings.stream().filter(string -> string.isEmpty()).count();


limit()

The ‘limit’ method is used to reduce the size of the stream. The following code segment shows how to print 10 random numbers using limit.

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);


sorted()

The ‘sorted’ method is used to sort the stream. The following code segment shows how to print 10 random numbers in a sorted order.

Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

Parallel Processing :- 

parallelStream is the alternative of stream for parallel processing. Take a look at the following code segment that prints a count of empty strings using parallelStream.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

It is very easy to switch between sequential and parallel streams.

Collectors :- 

Collectors are used to combine the result of processing on the elements of a stream. Collectors can be used to return a list or a string.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);

Statistics: -

With Java 8, statistics collectors are introduced to calculate all statistics when stream processing is being done.

List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest number in List : " + stats.getMax());
System.out.println("Lowest number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());

Stream Example :-

Create the following Java program using any editor of your choice in, say, C:\> JAVA.

Java8Tester.java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;


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

      System.out.println("Using Java 7: ");
      // Count empty strings
      List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
      System.out.println("List: " +strings);
      long count = getCountEmptyStringUsingJava7(strings);
      System.out.println("Empty Strings: " + count);

      count = getCountLength3UsingJava7(strings);
      System.out.println("Strings of length 3: " + count);

      //Eliminate empty string
      List<String> filtered = deleteEmptyStringsUsingJava7(strings);
      System.out.println("Filtered List: " + filtered);

      //Eliminate empty string and join using comma.
      String mergedString = getMergedStringUsingJava7(strings,", ");
      System.out.println("Merged String: " + mergedString);

      List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
      //get list of square of distinct numbers
      List<Integer> squaresList = getSquares(numbers);
      System.out.println("Squares List: " + squaresList);
      List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
      System.out.println("List: " +integers);
      System.out.println("Highest number in List : " + getMax(integers));
      System.out.println("Lowest number in List : " + getMin(integers));
      System.out.println("Sum of all numbers : " + getSum(integers));
      System.out.println("Average of all numbers : " + getAverage(integers));
      
     System.out.println("Random Numbers: ");
      //print ten random numbers
      Random random = new Random();
      for(int i = 0; i < 10; i++) {
         System.out.println(random.nextInt());
      }

      System.out.println("Using Java 8: ");
      System.out.println("List: " +strings);
      count = strings.stream().filter(string->string.isEmpty()).count();
      System.out.println("Empty Strings: " + count);

      count = strings.stream().filter(string -> string.length() == 3).count();
      System.out.println("Strings of length 3: " + count);

      filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
      System.out.println("Filtered List: " + filtered);

      mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
      System.out.println("Merged String: " + mergedString);

      squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
      System.out.println("Squares List: " + squaresList);
      
      System.out.println("List: " +integers);
      IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
      System.out.println("Highest number in List : " + stats.getMax());
      System.out.println("Lowest number in List : " + stats.getMin());
      System.out.println("Sum of all numbers : " + stats.getSum());
      System.out.println("Average of all numbers : " + stats.getAverage());

      System.out.println("Random Numbers: ");
      random.ints().limit(10).sorted().forEach(System.out::println);

      //parallel processing
      count = strings.parallelStream().filter(string -> string.isEmpty()).count();
      System.out.println("Empty Strings: " + count);
   }

   private static int getCountEmptyStringUsingJava7(List<String> strings) {
      int count = 0;
      for(String string: strings) {
         if(string.isEmpty()) {
            count++;
         }
      }
      return count;
   }

   private static int getCountLength3UsingJava7(List<String> strings) {
      int count = 0;
      for(String string: strings) {
         if(string.length() == 3) {
            count++;
         }
      }
      return count;
   }

   private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
      List<String> filteredList = new ArrayList<String>();
      for(String string: strings) {
         if(!string.isEmpty()) {
             filteredList.add(string);
         }
      }
      return filteredList;
   }

   private static String getMergedStringUsingJava7(List<String> strings, String separator) {
      StringBuilder stringBuilder = new StringBuilder();
      for(String string: strings) {
         if(!string.isEmpty()) {
            stringBuilder.append(string);
            stringBuilder.append(separator);
         }
      }
      String mergedString = stringBuilder.toString();
      return mergedString.substring(0, mergedString.length()-2);
   }

   private static List<Integer> getSquares(List<Integer> numbers) {
      List<Integer> squaresList = new ArrayList<Integer>();
      for(Integer number: numbers) {
         Integer square = new Integer(number.intValue() * number.intValue());
         if(!squaresList.contains(square)) {
            squaresList.add(square);
         }
      }
      return squaresList;
   }

   private static int getMax(List<Integer> numbers) {
      int max = numbers.get(0);
      for(int i = 1;i < numbers.size();i++) {
         Integer number = numbers.get(i);
         if(number.intValue() > max) {
            max = number.intValue();
         }
      }
      return max;
   }

   private static int getMin(List<Integer> numbers) {
      int min = numbers.get(0);
      for(int i= 1;i < numbers.size();i++) {
         Integer number = numbers.get(i);
         if(number.intValue() < min) {
            min = number.intValue();
         }
      }
      return min;
   }

   private static int getSum(List numbers) {
      int sum = (int)(numbers.get(0));
      for(int i = 1;i < numbers.size();i++) {
         sum += (int)numbers.get(i);
      }
      return sum;
   }

   private static int getAverage(List<Integer> numbers) {
      return getSum(numbers) / numbers.size();
   }
}

Verify the Result:-

Compile the class using javac compiler as follows −

C:\JAVA>javac Java8Tester.java

Now run the Java8Tester as follows −

C:\JAVA>java Java8Tester

It should produce the following result −


Using Java 7:

List: [abc, , bc, efg, abcd, , jkl]

Empty Strings: 2

Strings of length 3: 3

Filtered List: [abc, bc, efg, abcd, jkl]

Merged String: abc, bc, efg, abcd, jkl

Squares List: [9, 4, 49, 25]

List: [1, 2, 13, 4, 15, 6, 17, 8, 19]

Highest number in List : 19

Lowest number in List : 1

Sum of all numbers : 85

Average of all numbers : 9

Random Numbers:

-1279735475

903418352

-1133928044

-1571118911

628530462

18407523

-881538250

-718932165

270259229

421676854

Using Java 8:

List: [abc, , bc, efg, abcd, , jkl]

Empty Strings: 2

Strings of length 3: 3

Filtered List: [abc, bc, efg, abcd, jkl]

Merged String: abc, bc, efg, abcd, jkl

Squares List: [9, 4, 49, 25]

List: [1, 2, 13, 4, 15, 6, 17, 8, 19]

Highest number in List : 19

Lowest number in List : 1

Sum of all numbers : 85

Average of all numbers : 9.444444444444445

Random Numbers:

-1009474951

-551240647

-2484714

181614550

933444268

1227850416

1579250773

1627454872

1683033687

1798939493

Empty Strings: 2

Various core operations over Streams?

There are broadly 3 types of operations that are carried over streams namely as follows as depicted from the image shown above:

  1. Intermediate operations
  2. Terminal operations
  3. Short-circuit operations

Let us do discuss out intermediate operations . So, there are 3 types of Intermediate operations which are as follows:

  • Operation 1: filter() method
  • Operation 2: map() method
  • Operation 3: sorted() method

As we already have studied in the above example of which we are trying to filter processed objects can be interpreted as filter() operation operated over streams. 

Later on from that processed filtered elements of objects, we are collecting the elements back to List using Collectors  with the help of Collectors.toList() method. 

Example:


// Java program to illustrate Intermediate Operations 
// in Streams 
  
// Importing required classes 
import java.io.*; 
import java.util.*; 
import java.util.stream.*; 
  
// Main class 
class Test { 
  
    // Main method 
    public static void main(String[] args) 
    { 
  
        // Creating an integer Arraylist to store marks 
        ArrayList<Integer> marks = new ArrayList<Integer>(); 
  
        // These are marks of the students 
        // Considering 5 students so input entries 
        marks.add(30); 
        marks.add(78); 
        marks.add(26); 
        marks.add(96); 
        marks.add(79); 
  
        // Printing the marks of the students before grace 
        System.out.println( 
            "Marks of students before grace : " + marks); 
  
        // Now we want to grace marks by 6 
        // using the streams to process over processing 
        // collection 
  
        // Using stream, we map every object and later 
        // collect to List 
        // and store them 
        List<Integer> updatedMarks 
            = marks.stream() 
                  .map(i -> i + 6) 
                  .collect(Collectors.toList()); 
  
        // Printing the marks of the students after grace 
        System.out.println( 
            "Marks of students  after grace : "
            + updatedMarks); 
    } 
}

Output

Marks of students before grace : [30, 78, 26, 96, 79]
Marks of students  after grace : [36, 84, 32, 102, 85]

Note: For every object if there is urgency to do some operations be it square, double or any other than only we need to use map() function  operation else try to use filter() function operation. 

Lets discuss one real life example:-

                                                    Real-life Example

Example 1: In general, daily world, whenever the data is fetching from the database, it is more likely we will be using collection. so there streams concept can be appliec to deal with processed data.

Now we will be discussing out real-time examples to interrelate streams in our life. Here we will be taking the most widely used namely as follows:

  1. Streams in a Grocery store
  2. Streams in mobile networking

Example 2: Streams in a Grocery store 

The above pictorial image has been provided is implemented in streams which is as follows: 


List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Example 3: Streams in mobile networking 

Similarly, we can go for another widely used concept that is our dealing with our mobile numbers. Here we will not be proposing out listings, simply will be demonstrating how the stream concept is invoked in mobile networking by various service providers across the globe.


Collection can hold any number of object so let ‘mobileNumber’ be a collection and let it be holding various mobile numbers say it be holding 100+ numbers as objects. 

Suppose now the only carrier named ‘Airtel’ whom with which we are supposed to send a message if there is any migration between states in a country. 

So here streams concept is applied as if while dealing with all mobile numbers we will look out for this carrier using the filter() method operation of streams. 

In this way, we are able to deliver the messages without looking out for all mobile numbers and then delivering the message which senses impractical if done so as by now we are already too late to deliver. 

In this way these intermediate operations namely filter(), collect(), map() help out in the real world. Processing becomes super simpler which is the necessity of today’s digital world.

Hope by now you the users come to realize the power of streams in java as if we have to do the same task we do need to map corresponding to every object, increasing in code length, decreasing optimality of our code. 

With the usage of streams, we are able to in a single line irrespective of elements contained in the object as with the concept of streams we are dealing with the object itself.

Note: filter, sorted, and map, which can be connected together to form a pipeline.