Photo by Jeffrey Wegrzyn on Unsplash

Stream API is data flows from one side to other side.

Stream API is introduced in Java 8 in order to bring functional programming into Java.

  • It is a data pipeline.
  • It use to process collection of objects or arrays.
  • It will not changed the original source.
  • It is efficient in coding.

In Stream API there are basically three major components as follows.

  • Source — Stream source can be any type of collection, array etc.
  • Intermediate Operations — In this, the incoming data stream will be converted according to the intermediate operation. Some intermediate operations are sort, map, filter.
  • Terminal Operations — This is the final operation of the stream. It will either return void, a single value or a collection. Some terminal operations are forEach, reduce, collect, min, sum, max, average.

Let’s do an example and figure it out. We need to print female student names whose gpa is greater than 3.6.

  • Student.java
public class Student {
private String name;
private int age;
private Gender gender;
private double gpa;

public Student(String name, int age, Gender gender, double gpa){
this.name = name;
this.age = age;
this.gender = gender;
this.gpa = gpa;
}

public String getName() { return name; }

public void setName(String name) { this.name = name; }

public int getAge() { return age; }

public void setAge(int age) { this.age = age; }

public Gender getGender() { return gender; }

public void setGender(Gender gender) { this.gender = gender; }

public double getGpa() { return gpa; }

public void setGpa(double gpa) { this.gpa = gpa; }
}
  • Gender.java
public enum Gender {
MALE,
FEMALE
}
  • Stream.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Stream {

public static void main(String[] args) {
Student student1 = new Student("Yashod", 25, Gender.MALE, 3.3);
Student student2 = new Student("Shawn", 26, Gender.MALE, 3.6);
Student student3 = new Student("Hasini", 26, Gender.FEMALE,
3.7);
Student student4 = new Student("Chamindika", 24, Gender.FEMALE,
3.5);
Student student5 = new Student("Udayanga", 25, Gender.MALE,
3.4);

List<Student> studentList = Arrays.asList(student1, student2,
student3, student4,
student5);

List<Student> femaleList = new ArrayList<>();

for (Student student: studentList) {
if (student.getGender() == Gender.FEMALE
&& student.getGpa() > 3.6) {
femaleList.add(student);
}
}

for (Student student: femaleList) {
System.out.println(student.getName());
}

}
}

Let’s do this using Stream API.

import java.util.Arrays;
import java.util.List;

public class Stream {

public static void main(String[] args) {
Student student1 = new Student("Yashod", 25, Gender.MALE, 3.3);
Student student2 = new Student("Shawn", 26, Gender.MALE, 3.6);
Student student3 = new Student("Hasini", 26, Gender.FEMALE,
3.7);
Student student4 = new Student("Chamindika", 24, Gender.FEMALE,
3.5);
Student student5 = new Student("Udayanga", 25, Gender.MALE,
3.4);

List<Student> studentList = Arrays.asList(student1, student2,
student3, student4,
student5);
studentList.stream()
.filter(x->x.getGender() == Gender.FEMALE)
.forEach(x -> System.out.println(x.getName()));

}
}

Wow that is easy. Yes it is Let’s explore the operations in Stream API.

Operations in Stream API

First let’s start with how to make a stream. There are several method of doing this.

  • For Collections collectionIdentifier.stream()
List<String> names = Arrays.asList("Yashod", "Shawn", "Hasini");
names.stream().forEach(System.out::println);
  • For Arrays Arrays.stream(arrayName)
int[] numbers = {1, 2, 3, 4, 5, 6, 7};
Arrays.stream(numbers).forEach(System.out::println);

Note: System.out::println is same as x -> System.out.println(x) .

Then we will move to some intermediate operations.

This is used to sort the input stream. It will either sort in alphabetical order or it can be sorted using attribute.

  • Primitive types
int[] numbers = {4, 2, 6, 8, 10, 12};
Arrays.stream(numbers).sorted().forEach(x -> System.out.println(x));
  • Sorted with specific attribute in an object — Following will order the students according to the age of the students.
studentList.stream()
.sorted(Comparator.comparing(Student::getAge))
.forEach(System.out::println);
  • Sorted with more than one attribute in an object — Following first sort by the age and then sort by the gpa.
studentList.stream()
.sorted(Comparator.comparing(Student::getAge)
.thenComparing(Student::getGpa))

.forEach(System.out::println);

In Filter operation input stream will filter using a condition and pass the filtered values forward.Stream.filter(x -> condition) .

  • Primitive type — This will filter the number greater than 3.
Arrays.stream(numbers)
.filter(x -> x > 3)
.forEach(x -> System.out.println(x));
  • Filter with single attribute of an Object— This will filter students whose age is greater than 24.
studentList.stream()
.filter(x -> x.getAge() > 24)
.forEach(System.out::println);
  • Filter with multiple attributes of an Object — This will filter students whose age is greater than 24 and GPA is higher than 3.5.
studentList.stream()
.filter(x -> x.getAge() > 24 && x.getGpa() > 3.5)
.forEach(System.out::println);

Map Operation is used to update or alter the input stream and output altered stream. map(inputValue -> outputValue)

  • Primitive Type — This will convert the input numbers to its squares.
Arrays.stream(numbers)
.map(x -> x * x)
.forEach(x -> System.out.println(x));
  • Map through the objects — Increment the age of the Student by one.
studentList.stream()
.map(x -> {
x.setAge(x.getAge() + 1);
return x;
})

.forEach(System.out::println);

Above map function will get the input object one by one and increase the age by one and return the object to the next operation.

There are lots of intermediate operations and you can find when you need some are listed below.

  • findFirst()
  • skip()
  • peek()
  • flatMap()
  • distinct()

Then We will move to terminal operations.

This is one of a popular terminal operation which returns void. It is commonly used for printing.

Arrays.stream(numbers)
.forEach(x -> System.out.println(x));

This will return a Collection. We can get a List, Map etc as we need and we will dive into most needed use cases of this operation.

  • Get a list
List<String> filteredNames = Arrays.stream(names)
.filter(x->x.startsWith("S"))
.collect(Collectors.toList());
  • Get a Map

For a map we need to provide to lambda functions one is for the key and other is for the value.

Map<String, Integer> mapped 
= studentList.stream()
.filter(x -> x.getAge() > 25)
.collect(Collectors.toMap(
x->x.getName(),
x->x.getAge()
));

for (String key: mapped.keySet()) {
System.out.println("Name :" + key + " and Age:"
+ mapped.get(key));
}
  • Group by an using a key
Map<Integer, List<Student>> groupMap 
= studentList.stream()
.filter(x -> x.getAge() > 25)
.collect(Collectors.groupingBy(
Student::getAge
));

for (Integer key: groupMap.keySet()) {
System.out.println("For Age :" + key);
for (Student student: groupMap.get(key)) {
System.out.println("Name :" + student.getName());
}
System.out.println("------------");
}

Above will group the Students using their age.

Reduce Operation will summarise the elements as we command as follows and reduce is having initial value, updated value holder and incoming value. Reduce function will return exactly one value.

reduce(initialvalue, (updatedValue, IncomingValue) -> operation);

Following is some example to add square of elements using reduce function.

int sumOfSquares 
= Arrays.stream(numbers).reduce(0, (int a, int b) -> a + b * b);

There are lots of intermediate operations and you can find when you need some are listed below.

  • sum()
  • min()
  • max()
  • average()
  • summaryStatistics()

Hopefully this is helpful.

If you have found this helpful please hit that 👏 and share it on social media :).

Technical Writer | Tech Enthusiast | Open source contributor