Improve your Java code using - Stream API - Part 2
In this second part of a series on the Stream API, we will examine mapping operations and the various ways to transform a stream.
Mapping Operations in Stream
Mapping operations provide a method to transform the elements of a stream, resulting in a new stream with the transformed elements.
There are many methods we can use to transform a stream into another stream object, but the most common ones are map() and flatMap().
About map()
The map() method accepts a lambda expression as an argument and uses it to individually alter each element in the stream. The result is a new stream object with the transformed elements.
Below we can see the method definition:
<R>Stream<R> map(Function<? super T, ? extends R> mapper)
Input Parameter -> A function to apply to each element.
Return Type -> Yields a stream that contains the outcomes of implementing the specified function to each element of the initial stream.
Alright, now let’s examine a simple example of map(). In this example, we have a list of names. However, we need to print these names in upper case, and we’re going to use map() to accomplish this.
import java.util.ArrayList;
import java.util.List;
public class StreamMapExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Anakin");
list.add("Luke");
list.add("Palpatine");
list.add("Mando");
list.add("Leia");
// map() is used to convert each name to upper case.
// The map() method does not modify the original list.
list.stream()
.map(name -> name.toUpperCase()) //map() takes an input of Function<T, R> type.
.forEach(System.out::println); // forEach() takes an input of Consumer type.
}
}
About mapToInt()
To understand better, let’s take a look in one more example.
Given a list of names, we need to print the length of each word.
We can use map()
to solve this problem, which takes lambda expression as input and with .length()
method right?
However, have you noticed anything here?
The input is a string, and output is an integer. If we use map(names -> names.length())
, this should return a stream of integers.
But before, we discussed that if we are dealing with primitives then we should use primitive flavors of stream.
In this case, it’s time to use the mapToInt()
method.
When we use the mapToInt()
method instead of map()
, it will return IntStream instead of Stream.
So, if we assume that our function is going to return a primitive, instead of use map()
we should use mapToInt()
, mapToLong()
,
or mapToDouble()
.
import java.util.ArrayList;
import java.util.List;
public class StreamMapExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Logan");
names.add("Scott");
names.add("Jean");
names.add("Ororo");
names.add("Xavier");
names.stream()
.mapToInt(n -> n.length())
.forEach(System.out::println);
}
}
About flatMap()
When we need to flatten a stream of collections to a stream of elements combined from all collections, it’s time to
flatMap()
comes into the picture here.
Basically flatMap() used to do this of operations:
Stream<String[]> -> flatMap -> Stream<String>
Stream<Set<String>> -> flatMap -> Stream<String>
Stream<List<String>> -> flatMap -> Stream<String>
So, maybe you have to question yourself, why do we need to flatten our stream?
It’s because the intermediate methods like a filter()
and distinct()
do not work on streams of Collections.
These methods only work with primitives and objects streams. That’s why we need to flatten our stream before using these intermediate functions.
Ok, now let’s take a look in example to solve a problem.
We need to filter the strings and then print the filtered strings. But we have a Collection List<List<String>>
.
We will use flatMap() to solve this.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamFlatMapExample {
public static void main(String[] args) {
List<List<String>> stringList = new ArrayList<>();
stringList.add(Arrays.asList("a","b","c"));
stringList.add(Arrays.asList("d","e","f"));
stringList.add(Arrays.asList("g","h","i"));
stringList.add(Arrays.asList("j","k","l"));
//Created a stream from the list
Stream<List<String>> stream1 = stringList.stream();
// Flattened the stream.
//Stream<String> stream2 = stream1.flatMap(s -> s.stream());
Stream<String> stream2 = stream1.flatMap(Collection::stream);
//Applied filter on flattened stream.
//Stream<String> stream3 = stream2.filter(x -> "a".equals(x));
Stream<String> stream3 = stream2.filter("a"::equals);
stream3.forEach(System.out::println);
// All the code above can be written in a concise format like this
stringList.stream()
.flatMap(Collection::stream)
.filter("a"::equals)
.forEach(System.out::println);
}
}
Conclusion
Throughout this article, we’ve explored the powerful mapping operations available in Java’s Stream API. With map(),
we can easily transform a stream’s elements, and mapToInt()
, mapToLong()
, and mapToDouble()
,
provide an efficient way to work with primitive data types, producing streams of integers, longs, and doubles respectively.
Additionally, we learned about the flatMap() function and how it effectively flattens a stream of collections into a stream of elements.
Remember, the journey of learning never ends. Keep coding, and wait for next part of the series about Stream API.