Stream API In Java 8

Salitha Chathuranga
6 min readMay 26, 2022

--

Let’s manage data with streams

Hi guys! Have you ever heard of Java Streams? 😳 Actually it was introduced in Java 8.

Streams are used to process a collection of objects

So, we need some knowledge on Java Collections also, to work with Streams. When we need to do many intermediate operations and get a result based on a collection, streams can be used. It does not change the original data structure. And streams are not data structures also. Rather than storing data into variables and processing in several steps, we can do same thing using streams in one line!! Let’s see how to deal with streams now! 😊

NOTE: 📓

We should be familiar with Lambda expressions to get the real benefit of streams.

There are 2️⃣ main types of stream operations…

1. Non Terminal/Intermediate Operations

These operations are always returning another stream as a intermediate result.

map(), filter(), distinct(), sorted(), limit(), skip()

  • map — Apply some function an create a different output from the stream
  • filter — Select elements that are validated by a predicate
  • sorted — Sort the elements in the stream

2. Terminal Operations

These operations are returning a non-stream(cannot be chained) result such as primitive value, a collection or no value at all.

forEach(), toArray(), reduce(), collect(), min(), max(), count(), anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()

  • collect — Return the result as a list after stream operations
  • forEach — Iterate over the elements of the stream
  • reduce — Reduce the elements of the stream to a single value

We will look into all those operations with example codes next.. There is a huge set of operations available with streams! I will not be able to cover all those. I will cover all the basic ones and essentials.

Java provides a method called stream( ) to convert a collection into a stream of objects:

format => collection_reference.stream()

Otherwise we can use the Stream interface directly to create a stream on the go!

format => Stream.of(1,2,3,4,5)

Example uses of stream operations are given below… ✌️

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

public class StreamOperations {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(-1, 3, 5, 7, 9, 2, 4, 6, 8);
List<String> names = Arrays.asList("Java", "PHP", "JavaScript", "Python");

// MAP
List<Integer> mappedList = numbers.stream().map(x -> x * x).collect(Collectors.toList());
System.out.println("mappedList: " + mappedList);

// COLLECT
List<Integer> squaredNumbers = numbers.stream().map(x -> x * x).collect(Collectors.toList());
System.out.println("squaredNumbers: " + squaredNumbers);

// SORTED
List<Integer> sortedNumbers = numbers.stream().sorted().collect(Collectors.toList());
List<String> sortedNames = names.stream().sorted().collect(Collectors.toList());

List<String> sortedNamesDesc = names.stream().sorted((s1, s2) -> {
System.out.printf("sort: %s; %s\n", s1, s2);
return s2.compareTo(s1);
}).collect(Collectors.toList());

System.out.println("sortedNumbers: " + sortedNumbers);
System.out.println("sortedNames: " + sortedNames);
System.out.println("sortedNamesDesc: " + sortedNamesDesc);

// FILTER
List<Integer> evenNumbers = numbers.stream().filter(integer -> integer % 2 == 0).collect(Collectors.toList());
System.out.println("evenNumbers: " + evenNumbers);
List<String> filteredNames = names.stream().filter(name -> name.startsWith("J")).collect(Collectors.toList());
System.out.println("filteredNames: " + filteredNames);

Predicate<Integer> predicateToGetOddNumbers = integer -> integer % 2 != 0;
List<Integer> oddNumbersUsingPredicate = numbers.stream().filter(predicateToGetOddNumbers).collect(Collectors.toList());
System.out.println("oddNumbersUsingPredicate: " + oddNumbersUsingPredicate);

// FOREACH
numbers.stream().map(x -> x * 2).forEach(x -> System.out.print(x + " "));
System.out.println();

// REDUCE
int sum = numbers.stream().reduce(0, Integer::sum);
int max = numbers.stream().reduce(0, Integer::max);
int min = numbers.stream().reduce(0, Integer::min);
System.out.println("sum: "+sum);
System.out.println("max: "+max);
System.out.println("min: "+min);


}
}

Explanation of the examples related to operations:

▶️ Collect Operation

In this example code, I have used collect method in many places. Whenever I want to assign the intermediate resulting stream into a collection, I have used collect operation.

▶️ Map Operation

I have used this operation to covert the elements in the stream into another set of objects like squared numbers. Only thing we need to understand the way to define it.

▶️ Sorted Operation

I used sorted function to sort the integers and names. By default it uses the basic integer and string comparison. But we can change the implementation in this method. In the code, as as example I have defined sortedNamesDesc list as a result of names list in descending order.

▶️ Filter Operation

This is a very useful function that can be used to select elements what we want. Filter method takes a Predicate as a parameter. What is it? Predicate is a Functional Interface in Java. (I think you have basic understanding on Lambda Expressions!) Simply it has only one method which returns a boolean value. So, we can pass a lambda expression which returns a boolean value, as the parameter for filter method. I have defined predicateToGetOddNumbers variable as a Predicate to return true if the element is odd.

▶️ ForEach Operation

This method is also useful to loop over a collection. Simply it takes a Consumer as the argument. It can be separately implemented and provided into the forEach loop. Otherwise we can implement the logic as lambda expression, within the forEach loop.

Consumers

So, let’s see how to create a Consumer!

There are 2️⃣ main types of consumers.

1.Consumer

Consumer<String> changeCase = a -> System.out.println(a.toUpperCase());

This converts the string elements in a list, to UPPERCASE. So, we can just pass this consumer as following.

names.forEach(changeCase);

2. Bi Consumer

Actually its use case is related with Map data structure objects.

Map<Integer, String> map = new HashMap<>();
map.put(1, "a");
map.put(2, "b");
map.put(3, "c");
BiConsumer<Integer, String> biConsumer = (k, v) -> {
System.out.println(" Modified key = "+ (k+10) + " Value = "+ v.toUpperCase());
};

As previous example, we can pass this also to the relevant data structure. Here, it’s a map.

map.forEach(biConsumer);

So, this is about consumers. Instead of consumer, we can also just include the logic in consumer directly to the forEach loop!

▶️ Reduce Operation

When we need to convert the elements into one single result, we can use this method on streams. But how? Let me explain. There are some basic concepts needed to understood before using reduce method. Basically it takes two parameters. They are identity and accumulator.

identity
The initial value of the reduction and the default result if there are no elements in the stream.

accumulator
a function that takes two parameters : a partial result of the reduction operation

Let’s take an example. Imagine we need to get the sum of a collection of integers. So, in this case;
what is the initial result? sum = 0 -> identity
what is the function? sum = a + b
So, when this logic is implemented in stream reduce method, it will be like this.

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

To more clarify the accumulator, Java has introduced a simpler way to implement these using method references. According to that conversion simplification is like this.

int sum = numbers.stream().reduce(0, Integer::sum);

(a, b) -> a + b has been replaced by Integer::sum. Every time we can not do this. But Java gives you some basic logic inbuilt, as a support to reduce the code lines.

I think you have understood the concept!! If not, you can ask me on the comment section also…

OK Guys! Now I have fully explained the main operations used in Java 8 Streams. You may have already seen, how streams reduces the lines of code by chaining the operations on the fly. There are inbuilt operations that help a lot to process collections of objects without storing in variables intermediate. We can do various things by chaining these operations. To practice, I will some exercises here for you!

  1. Find sum of only the even numbers in a collection
  2. Display the words in a collection are palindrome or not (ex: abba is a palindrome, abc is not a palindrome: we get the same word when we read from both the ends)
  3. Find the maximum of the odd numbers in a collection (collection includes both odd and even numbers)
  4. Find the list of names of Person objects whose age greater than 25 (Person has name and age as properties)

Answers for the questions: 💪

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

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;
}

@Override
public String toString() {
return "Person{name=" + name + ", age=" + age + "}";
}
}

public class StreamExamples {

public static void main(String[] args) {

// 1
System.out.println(numbers.stream().filter(integer -> integer % 2 == 0).reduce(0, Integer::sum));

// 2
List<String> words = Arrays.asList("abc","aba", "aaa", "abba", "bcbd");
Consumer<String> consumer = s -> {
StringBuilder reversedStr = new StringBuilder(s).reverse();
if (reversedStr.toString().equals(s)){
System.out.println("This is a palindrome");
}
else {
System.out.println("This is not a palindrome");
}
};
words.forEach(consumer);

// 3
List<Person> personList = Arrays.asList(
new Person("Salitha", 26), new Person("Michael", 32), new Person("Jones", 24),
new Person("Andrew", 20), new Person("Sam", 50), new Person("Ben", 27)
);

List<Person> personResult = personList.stream().filter( person -> person.getAge() > 25).collect(Collectors.toList());
System.out.println("personResult: "+personResult);

// 4
System.out.println(numbers.stream().filter(integer -> integer % 2 != 0).reduce(0, Integer::max));

}
}

So practice guys! You will get to know more stuff then. I think I could provide you a proper guide on Streams. So if you have anything to get clarified, drop a comment 😄

Good Bye! Stay safe!!! ❤️

--

--

Salitha Chathuranga
Salitha Chathuranga

Written by Salitha Chathuranga

Associate Technical Lead at Sysco LABS | Senior Java Developer | Blogger

Responses (1)