Stream APIs in Java
What is Stream API in Java?
- Stream API was introduced in Java 8,it helps in working with the collections in functional and declarative way. There are different Stream Features in Java that we can use to manipulate and transform the data.
- Over a stream's lifetime, each of its individual components is only visited once. The same source components must be revisited by creating a new stream.
- The classes Stream, IntStream, LongStream, and DoubleStream are streams over objects and the primitive int, long, and double types.
- Stream procedures are classified as intermediate or terminal, and they are combined for the construction of stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel) and followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.reduce
Use case of Stream in Java
- It reduces the coding time and boiler plate code.
- It supports functional and declarative style of coding which suits the new programming styles.
- While working with Collections we need to create various loops and checks and given that in today's time multi core processors are available we want to leverage the parallel features but writing the code that will run in non sequential manner is difficult.
- Here is where Stream In Java come in place , Stream APIs help us to execute our operations in non sequential manner easily with the help of its in built functions.
- Streams facilitate parallel execution by reframing the computation as a pipeline of aggregate operations, rather than as imperative operations on each individual element.
- Streams takes collections as an input and process it to compute the result.
How to create Streams from Collections in Java?
Collections have provided us with methods that we can use to create streams
from the collections, we can use stream() , parallelStream() methods.
// ArrayList of Strings
ArrayList<String> arrayListOfStrings=
new ArrayList<>();
arrayListOfStrings.add("Welcome");
arrayListOfStrings.add("to");
arrayListOfStrings.add("KodeSrc!");
// Use stream(),parallelStream()
Stream streamOfStrings=
arrayListOfStrings.stream();
Stream parallelStreamOfStrings=
arrayListOfStrings.parallelStream();
stream() Method in Java Collections: When we call this particular method on
any collections it will return a sequential Stream with that collection as its
source.
parallelStream() Method in Java Collections: When we call this particular
method on any collections it will return a parallel Stream with that
collection as its source. Internally it uses ForkJoin Framework to run the
operations in parallel.
- From an array via java.util.Arrays.stream(Object[]);
- From methods on the stream classes, such as Stream.of(Object[]),Stream.iterate(Object, UnaryOperator);
- Streams of random numbers can be generated from java.util.Random.ints();
Stream API filter() method in Java:
The filter() method in stream API takes a predicate as a parameter, it is a
clause statement on which each element of the collection will be processed and
a new intermediate stream will be generated.
Lets try to understand with some examples
Stream API filter() method in Java Example 1:
Given below list of strings you need to filter out / remove the string which
has exclamation mark (!)
import java.util.ArrayList;
import java.util.List;
public class StreamTutorial {
public static void main(
String[] args
) {
// ArrayList of Strings
ArrayList<String> arrayListOfStrings =
new ArrayList<>();
arrayListOfStrings.add("Welcome");
arrayListOfStrings.add("to");
arrayListOfStrings.add("KodeSrc!");
// Use stream()
List<String> arrayList =
arrayListOfStrings
.stream()
.filter(
e -> !e.contains("!")
)
.toList();
for (String e : arrayList) {
System.out.printf(e + " ");
}
}
}
Stream API filter() method in Java Example 2:
Given list of integers filter out the elements who are less than 0 i.e have
negative values.
public static void filterOutNegativeElements() {
List<Integer> integerArrayList =
Arrays.asList(1, -2, 4, 5, 3, -6, 8, 6, 9, -1);
integerArrayList
.stream()
.filter(
e -> e < 0)
.forEach
(
e -> System.out.println(e)
);
}
Here i have chained the stream with forEach method to avoid the for loop to
just print the data. You can use the toList() or toArray() methods and
collect the result into a new collection.
To deep dive into filter() method and to get more example code see here
Stream API count() method in Java:
As the name suggests the count() method in stream API is used to find the
count of elements in the stream, it can be used independently on a stream or
in chain of stream operations. count() method is one of the terminal method in
stream API.
Stream API count() method in Java Example:
Given a list of integers find out the count of elements who are greater than a
particular number. We will use
filter() method in combination with count() method.
In the below example if we pass 5 as the input integer we will get the count
as 3 as 3 elements are greater than 5.
public static void countNumbersGreaterThanX(
int integerToCompareWith
)
{
List<Integer> integerList =
Arrays.asList(9,7,-2, 4, 15);
Long countOfNumbers=integerList
.stream()
.filter(
e -> e > integerToCompareWith
).count();
System.out.println(
"countOfNumber= "+countOfNumbers
);
}
Before diving into other features of Stream APIs lets see what is Stream operations and pipelines?
What is Stream Operations and Pipelines?
The Stream are nothing but a sequence of data elements and the tasks like
filtering, mapping, reducing etc are considered to the the Stream Operations .
These Stream Operations when put together they form a chain of commands which
constitutes to creation of Stream Pipeline.
Stream Operations are of 2 type intermediate and terminal operations.
Intermediate Operation:
- These includes operations like filter, map etc which performs a task and in return gives a new stream.
- These are considered to be lazy operations.
Terminal Operation:
- These included operations like reduce().
- After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used,if you need to traverse the same data source again, you must return to the data source to get a new stream.
- In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.
Stream API findAny(), findFirst() method in Java:
The findAny() method of java stream API returns an Optional<type> ,
we cannot determine which element will be selected from the stream ,
findAny() method is free to select any element from the stream. But if the
stream is empty it will return an empty Optional.
The findFirst() method of java stream API returns an Optional<type>,
it picks up the first element from the stream and returns it.
public static void useOfFindAnyFindFirstMethod()
{
List<String> listOfElements =
Arrays.
asList("KodeSrc","!","1");
Optional<String> foundedElement=
listOfElements
.stream()
.findAny();
System.out.println(
"foundedElement: "+foundedElement
);
foundedElement=
listOfElements
.stream()
.findFirst();
System.out.println(
"foundedElement: "+foundedElement
);
}
Stream API limit() method in Java:
The limit() method of stream API will limit the number of elements
returned from a stream, for example you are processing a large amount of
data and you want to process the data in batches so you can limit the data
in chunks and then process it.
public static void limitMethodUseCase()
{
List<String> listOfElements=Arrays.
asList("a","b","c","d","e","f");
List<String> limitedElements=listOfElements
.stream()
.limit(4)
.toList();
for(String e:limitedElements)
{
System.out.println(e);
}
}
This will limit the elements to 4 and return a new list with "a,b,c,d"
elements.
You can also cross check the size by using the size method of the list or
the
count()
method in stream chain similar to the earlier example.
public static void limitMethodUseCase()
{
List<String> listOfElements=Arrays.
asList("a","b","c","d","e","f");
List<String> limitedElements=listOfElements
.stream()
.limit(4)
.toList();
for(String e:limitedElements)
{
System.out.println(e);
}
Long countOfLimitedElements=listOfElements
.stream()
.limit(4)
.count();
System.out.println(countOfLimitedElements);
}
Features of Stream API in Java
- Streams in java does not store the elements. The operations are performed on the go and computed, it goes through a pipeline of computational tasks and returns a new stream as a result.
- While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.
- The Operations executed on a stream does not impact it's source i.e if we modify or do any computation on a stream it will create a new source of data and will not impact the existing source.This allows us to create multiple instances from a single stream source.
- Stream evaluates the code as and when required, it's lazy in nature.
- As Streams executes in lazy manner and returns another Stream in result , therefore we can chain different operations/tasks and return a final result stream at the end.
- Stream in Java supports operations like filter, map, limit, reduce, find, match, etc.
- Using Stream we can convert one data structure to another data structure with ease.
- As Stream takes collections in input the iterations are done internally which is one of the use case of Streams as compared to Collections where explicit iterator is needed.
Stream API skip() method in Java:
As the name speaks itself, the skip() method of stream API in java will
skip a particular number of elements from the stream and proceed with the
next elements. If the skip is more then the size of stream it will return
a new empty stream.
In the below example it will skip the first 4 elements are process the
other, so the new list will have "e,f" in it and size will be 2.
public static void skipMethodUseCase()
{
List<String> listOfElements=Arrays.
asList("a","b","c","d","e","f");
List<String> elements=listOfElements
.stream()
.skip(4)
.toList();
for(String e:elements)
{
System.out.println(e);
}
Long countOfElements=listOfElements
.stream()
.skip(4)
.count();
System.out.println(countOfElements);
}
Stream API limit() and skip() method in Java combined use case example:
The real of streams is to chain multiple operations to come up with a
result, it is the best use case of the Stream APIs of Java. We have seen
an example of limit() and skip() method each above. Now lets try to
combine this both methods to try to use it for one of the real world use
case.
Say you have a very big set of data that you need to process but if you
try to process it in single go it is giving memory issues.So in such cases
we could leverage the skip() and limit() methods.
For example consider the below code, we have used just 20 data points but
image it is huge and we need to process it in batches. So we can process
it in the following way.
Initially set the Skip to 0 and limit whatever batch size we want here we
kept it 5 as batch size. Now run a while loop till the skip reaches the
end of the data sets.
Now in the loop every time apply skip and limit to the stream and perform
whatever operation you need to perform. Here we just simply printed out
the values.
Now increment the skip to the next value the next skip value will be you
skip + limit i.e you batch size.
The loop will stop as soon as all data points are processed.
public static void limitSkipMethodCombinedUseCase()
{
//Create data set for example
List<Integer> listOfElements=Arrays.
asList(
0,5,74,
96,124,145,
222,280,136,
198,70,110,
204,481,434,
420,336,578,
324,608
);
int skip=0;
int limit=5;
while(skip<listOfElements.size()){
System.out.println(
"Skip "+skip
);
listOfElements
.stream()
.skip(skip)
.limit(limit)
.forEach(
//Your main operation could be here
e->System.out.println(e)
);
skip=skip+limit;
}
}
Stream API map() method in Java:
The map() method in stream APIs is used to map the elements with its
result.
In Simple term map() method works like this:
- Takes elements from the stream one by one.
- Apply/Perform the operation specified in the mapper function on each element.
- It takes a lamda function as a parameter
- Move to the next element.
To understand it better lets see small code below.
Here we have taken a list of strings and now we need to add exclamation
mark at end of every string. To do so we can use a loop or use map()
method given in stream API of java.
public static void mapMethodUseCase()
{
List<String> listOfElements=Arrays.
asList("a","b","c","d","e","f");
List<String> elements=listOfElements
.stream()
.map(
e->e.concat("!")
)
.toList();
for(String e:elements)
{
System.out.println(e);
}
}
You might argue why we need to use map() method and not just go with
normal loop, yes you can but as we mentioned earlier the real power of
stream APIs is in chained operations where you need to perform different
chained tasks on the collection one after other.
Doing this with help of normal loops would be difficult and will have a
lot of code.
But using Stream APIs we can avoid those boiler code
Stream API filter() and map() method in Java example:
To understand the real use of map() method let's chain it with the
filter() method.
Say you have a list of students who has given an exam and you have there
marks with you, now as per normal process you need to give 5 marks
increment to the students who has got marks more than 20 but less than
40.
Here you can leverage the
filter() and map() method provided by Stream APIs in Java.
Student Class:
class Student{
private int rollNumber;
private String name;
private int marks;
public Student(int rollNumber, String name, int marks) {
this.rollNumber = rollNumber;
this.name = name;
this.marks = marks;
}
public int getMarks() {
return marks;
}
public void setMarks(int marks) {
this.marks = marks;
}
public int getRollNumber() {
return rollNumber;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"rollNumber=" + rollNumber +
", name='" + name + '\'' +
", marks=" + marks +
'}';
}
}
Method:
public static void filterAndMapMethodUseCase()
{
List<Student> listOfStudents=Arrays.
asList(
new Student(1,"s1",38),
new Student(2,"s2",70),
new Student(3,"s3",29),
new Student(4,"s4",89),
new Student(5,"s5",30),
new Student(6,"s6",90)
);
List<Student> studentsWithIncrementGiven=listOfStudents
.stream()
.filter(
student->
student.getMarks()>20
&&
student.getMarks()<40
)
.map(
student -> {
student.setMarks
(
student.getMarks()+5
);
return student;
}
)
.toList();
for(Student e:studentsWithIncrementGiven)
{
System.out.println(e);
}
}
As you can see with less code we have performed our task.
Output:
Stream API reduce() method in Java:
Say you have a list of integers and you want to find out the min of the
collections you can do that using the reduce() method.
Example below lets see how to do sum using the stream reduce method.
public static void reduceMethodUseCase(){
List<Integer> listOfElements=Arrays.
asList(
0,5,74
);
int result=listOfElements
.stream()
.reduce(
(i,j)->i+j
)
.get()
.intValue();
System.out.println(result);
}
This will print 79 as output.
Stream API reduce() method in Java other example:
The below code will give output as -60.
public static void productOfAllNegativeNumbers()
{
List<Integer> list=Arrays
.asList(2,-3,-4,6,-5);
int result=list
.stream()
.filter(e->e<0)
.reduce(
1,(a,b)->a*b
)
.intValue();
System.out.println("Product of negative numbers: "+result);
}
Output:
Feel free to reach us out !
Interested in understanding few more topics in Java?
Check it Out !
Please Let me Know, If you have any doubts.
Please Let me Know, If you have any doubts.