Stream API
Stream API concept was first introduced in Java 8 which is used to process collections of objects under the java.util.Stream package. This package consists of classes, interfaces which allow functional-style operation on the elements. By using Stream we can perform various aggregate operations on the data returned from collections, arrays, I/O operations i.e we can process data in a declarative way similar to SQL statements. A stream does not store any data and it means it is not any underlying data structure for an object. The first Question comes to our mind why this API was introduced.

Let’s see an example if we want to iterate a list of list of String that starts with ‘a’.

 

 

package com.amitclive.stream;

import java.util.ArrayList;
import java.util.List;

public class WithoutStream {
 public static void main(String[] args) {
 
 List<String> names= new ArrayList<String>();
 names.add("amit");
 names.add("anthony");
 names.add("annabelle");
 names.add("juno");
 names.add("Suno");
 names.add("aela");

 

Implementation without Streams:

 

 int count =0;
 for(String str:names){
 if(str.startsWith("a")){
 count++;
 }
 }
 System.out.println("no of elements starts with a are: "+count);

 

Implementation with Streams:

 long count1 =names.stream().filter(str -> str.startsWith("a")).count();
 System.out.println("no of elements starts with a are: "+count1);
 }

}

 

Are you able to figure out the difference here?
Both the implementation has the same output But if you see the code there is a major difference in the number of lines of code and the performance impact.

In the first implementation without the stream, we are iterating the list of String to find out the string which starts with “a”. This is a sequential execution where we are not taking any advantage of the processor.

In the second implementation with the stream, we are using stream method which will return stream of all names, filter() methods return another stream based on the filtering condition and count() method return the number of elements we have after filtering. Here we are taking advantage of the processor because this is happening parallelly with the help of streams.

Parallel execution is always faster than the sequential execution.

 

Now Let’s see the Feature of Stream:

-> A Stream doesn’t store data and is not a data structure, it conveys element from a source such as a data structure, an array, collection object, and any I/O channel, through a pipeline of computational operations.

-> Stream enables the functional style programming since it is functional in nature. When we perform any operation on stream produced from any source, it doesn’t modify the source, they just provide the result as per the pipelined method used.

->Stream is lazy and evaluates code only when required i.e operations like filtering, mapping or duplicate removal can be implemented lazily and return a stream of results.

-> Stream supports aggregate operations like filter, map, limit, reduce, find, match. we will study in detail about this.

-> The elements of a stream are only visited once during the life of a stream. Like an iterator, a new stream must be generated to revisit the same element of the source.

You must be wondering Is Stream or Collection same? Are you?

Now Let’s see the difference between Collection and Stream.

-> Collections are a data structure which holds data where Streams are not a data structure and it doesn’t hold any data.

-> We can perform an aggregate operation on stream with the help of inbuilt method but we don’t have such methods in Collections.

-> Stream is computed based on the demand but In Collection all the elements are populated before we start using Collection.

-> Stream removes a lot of boilerplate code and enables functional programming whereas In Collection to perform the same operation we need to write a number of lines of code.

-> Streams are more powerful in terms of performance as it supports sequential as well as parallel processing but Collection doesn’t support parallel processing.

 

How Stream Works in Java?

In the above examples, we have seen the code now let’s understand how it worked in steps.

STEP1: Create a stream

STEP2: Perform Intermediate operations

This operation is used to transform it into another stream and then perform an intermediate operation on that stream. Above we can see we have filter() method used for this.

STEP3: Perform Terminal Operations

This is used to get the final result. In the above example, we see count() method is used.

 

Creation of Stream:

->In Java 8, Collection interface has two methods to generate a Stream.

1.Using stream() method that returns a sequential stream

2.Using parallelStream() method that returns a parallel stream.

-> From an array using Arrays.stream(Object[])

-> From static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int,int) or Stream.iterate(Object, UnaryOperator);

-> In case of I/O Operation, The lines of a file can be obtained from BufferReader.lines();

-> Stream of random numbers can be obtained from Random.ints();

->There are numerous other stream -bearing methods in the JDK including BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence) and JarFile.stream().

 

Intermediate Operations:

       1 .map(): The map method is used to map the items from the collections to other objects according to the Predicate passed as arguments.

Stream<String> product =Stream.of("SHOE","TSHIRT","OVEN","HOUSEHOLD");

System.out.println(product.map(str -> {return str.toLowerCase();}).collect(Collectors.toList()));

  2. filter():  The filter() method is used to select the elements as per the Predicate passed as an argument and generate the filtered list.

Stream<Integer> num = Stream.of(1,5,10,20,30,40,50);

Stream<Integer> filterNum = num.filter(n ->  n<25);

filterNum.forEach(s -> System.out.println(s +" "));

  3. sorted(): The sorted method is used to sort the stream.

Stream<String> names =Stream.of("ane" ,"amit" ,"juyas" ,"sapit", "nijah");

List<String> nameSorted =names.sorted().collect(Collectors.toList());

  4. flatMap(): This method is used to create a stream from the stream of a list.

Stream<List<String>> nameList =Stream.of(Arrays.asList("zoya"),Arrays.asList("Gogin"),Arrays.asList("David"));

Stream<String> flatStream = nameList.flatMap(strList -> strList.stream());

flatStream.forEach(System.out:println);

 

Terminal Operations:

1. collect: The collect method is used to return the result of the intermediate operation performed on the stream.

List<Integer> num =Arrays.asList(1,2,3,4,5,6,7,8,9);
Set<Integer> set =num.stream().map(n -> n*n).collect(Collectors.toSet());
System.out.println(set);

 

2. forEach : This method is used to iterate over every element of the stream.

List<Integer> number =Arrays.asList(1,2,3,4,5);
number.stream().map(n -> n+1).forEach(s -> System.out.println(s));

 

3. reduce: This method is used to reduce the element of a stream to a single value. The reduce method takes a BinaryOperator as a parameter.

List<Integer> numList =Arrays.asList(1,2,3,4,5,6);
int no =numList.stream().filter(x -> x%2 == 0).reduce(0, (a,i) -> a+i);
System.out.println(no);

4. count: This method is used to count the number of items in the stream.

List<Integer> numList =Arrays.asList(1,2,3,4,5,6,7);
System.out.println(numList.stream().count());

5. match: This method is used to find the match for the given conditions that return boolean value.

List<Integer> numList =Arrays.asList(1,2,3,4,5,6,7);
System.out.println(numList.stream().anyMatch(i -> i==3));

6. findFirst: This method is used to find the first element based on the given condition.

 

Stream<String> str =Stream.of("Ai","Java","Hadoop","Data","ML","Aero");
Optional<String> firstNameWithA = str.filter(i -> i.startsWith("A")).findFirst();
if(firstNameWithA.isPresent()){
System.out.println(firstNameWithA.get());
}

 

Some operations are deemed short-circuiting operations which allow computations on infinite streams to complete in finite time.

Stream<Integer> infiniteStream = Stream.iterate(1, i-> i*2);
List<Integer> collect = infiniteStream.skip(2).limit(2).collect(Collectors.toList());
collect.forEach(n -> System.out.println(n));

Here we use short-circuiting operations skip() method to skip the first 2 elements and limit() method is used to limit to 2 elements from infinite stream generated using iterate().

 

 

Comparision Based Stream Operations:

1. sorted(): This method is used to sort the stream elements based on the comparator passed we pass into it.

List<String> jobs =Arrays.asList("BusinessMan","SoftwareDeveloper","Influencer");
List<String> jobList =jobs.stream().sorted((e1,e2) -> e1.compareTo(e2)).collect(Collectors.toList());
jobList.forEach(s -> System.out.println(s));

 

2. max and min: These methods are used to return the maximum and minimum element in the stream respectively based on a comparator.

3. distinct: This method does not take any argument and return the distinct elements in the stream, eliminates the duplicates. To find the distinct element it uses equals method.

 

4. allMatch, anyMatch, and noneMatch: These methods take a predicate and return a boolean value. Short-circuiting is applied and processing is stopped as soon as the answer is determined.

allMatch is used to match the predicate true for all the elements in the stream. It returns false if one element doesn’t match the conditions

anyMatch is used to match the predicate true for any element in the stream.

noneMatch is used if there are no elements matching the predicate.

 

This is not the end of Streams Concept. But If you are looking for Basic level You are good to go. We will come up with the Advanced concept So Stay Tuned for more.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.