All about Java Streams — a quick hands-on introduction
TheCodeAlchemist
Posted on March 14, 2023
Java 8 added great features to the Java language. Out of them, two stand out — Lambdas and Streams.
You can find all the video links here. This is a very detailed and hands-on playlist.
What is a Stream?
See this where I explain this in details.
In simple words — a stream is simply a sequence of optionally ordered values.
That’s it! What else?
Oh yes, before I forget — Stream doesn’t store the data, their purpose is to process the data with the help of pipelines that a programmer would write/set.
And, Streams are lazy that means pipeline is not executed until we call a terminal operation.
What do you mean by pipeline?
You can see a small demo here
Streams follow a declarative syntax, similar to a SQL query which is close to the problem statement. You write what to do rather than how to do it.
Additionally, we can chain multiple operations where each method represents a step, and this whole code is called a stream pipeline. See the example below and I’m sure you will appreciate the simplicity and clarity if you compare this with traditional Java code.
int sum = widgets.stream()
.filter(w -> w.getColor() ==RED)
.mapToInt(w -> w.getWeight())
.sum();
Stream Operations
Stream supports many methods. On a very high level, they can be categorized as below.
Creating a Stream
There are many ways to generate a Stream. We’ll be focusing on important ones.
Via Static factory methods, Stream.of(..) variants
// 1. static factory methods
System.out.println("Stream factory methods---------");
//Creating a stream of single element i.e. Hello
Stream.of("Hello").forEach(System.out::println);
System.out.println("------------------------");
//Creating a stream of multiple elemtns
Stream.of("Apple", "Banana", "Grapes").forEach(System.out::println);
System.out.println("------------------------");
//When source is null
Stream.ofNullable(null).forEach(System.out::println);
System.out.println("------------------------");
From Arrays using Arrays.stream(..) method
// 2. From Arrays
System.out.println("From arrays--------------------");
String[] names = {"Mike", "John", "Tom"};
Arrays.stream(names).forEach(System.out::println);
From collections using stream() method
Most of the collections have a stream() method that can be used to create a new stream.
// 3. From Collection
System.out.println("From collections---------------");
List<String> languages = List.of("Java", "Python");
languages.stream().forEach(System.out::println); //by calling stream()
From Files
IO API has been enhanced to return streams. Here’s an example, where we’re reading a file using Stream.
// 4. Files
System.out.println("From Files---------------------");
Files.readAllLines(Paths.get("src\\dummy.txt"))
.stream()
.forEach(System.out::println);
Using map() and flatMap()
See this demo to understand how to use these methods.
map() is used to transform an object or value. For instance, transform a stream of Movie objects to a stream of movie titles.
flatMap() is used when the value/object we’re trying to transform is itself a collection/array so we need to flatten the object first in order to get the individual items from that collection/array.
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
MOVIES.stream()
.map(Movie::rating)
.forEach(System.out::println);
MOVIES.stream()
.flatMap(movie -> Arrays.stream(movie.sideCharacters()))
.forEach(System.out::println);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) { }
}
Using filter()
See the filter demo
Filters are used to apply a predicate to filter the records.
MOVIES.stream()
.filter(movie -> movie.releaseYear() > 2000)
.forEach(System.out::println);
Using peek() to debug
See the peek demo
Just like print statements but for Streams.
MOVIES.stream()
.peek(movie -> {
System.out.println("Before Filter:: "+movie);
})
.filter(movie -> movie.releaseYear() > 2000)
.peek(movie -> {
System.out.println("After Filter:: "+movie);
})
.forEach(System.out::println);
Using limit() and skip()
See the demo here
limit() is used to truncate the Stream while skip() can be used to skip/jump stream by n elements.
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
MOVIES.stream()
.limit(2) //will print first two but last movie in the list will be truncated
.forEach(System.out::println);
MOVIES.stream()
.skip(2) //will print last movie with id 1
.forEach(System.out::println);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) { }
}
Using sorted()
See the demo here
sorted() is used to sort the stream elements.
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
MOVIES.stream()
.sorted(Comparator.comparingInt(Movie::id)) //to sort on the basis of an int property i.e. movieID
.forEach(System.out::println);
MOVIES.stream()
.sorted(Comparator.comparing(Movie::title)) //to sort on the basis of title, will apply natural ordering of String
.forEach(System.out::println);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) { }
}
Using distinct()
See the demo here
distinct() is used to process distinct elements by avoiding duplicates.
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"}),
//added a duplicate movie
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
MOVIES.stream()
.distinct() //will use equals to compare objects via id
.forEach(System.out::println);
}
//added equals and hashcode to do a comparison on id
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Movie movie = (Movie) o;
return id == movie.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
}
Using forEach() and forEachOrdered()
See the demo here
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
MOVIES.parallelStream()
//order is not guaranteed in case of parallel stream
.forEach(System.out::println);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) { }
}
Output:
Movie[id=3, title=Matrix, director=Wachowski, character=Neo, rating=10, releaseYear=2000, sideCharacters=[Ljava.lang.String;@2ef1e4fa]
Movie[id=2, title=LOTR, director=Peter Jackson, character=Aragorn, rating=10, releaseYear=2000, sideCharacters=[Ljava.lang.String;@3b0971b2]
Movie[id=1, title=The Dark Knight, director=Nolan, character=Batman, rating=10, releaseYear=2008, sideCharacters=[Ljava.lang.String;@6b408627]
forEachOrdered() makes sure the order remains the same in case of parallel streams.
MOVIES.parallelStream()
.forEachOrdered(System.out::println);
Using findFirst() and findAny()
See the demo here
Depending on the filter applied, find*() methods try to find the matching element in the Stream.
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 9, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
Optional<Movie> m1 = MOVIES.stream()
.filter(movie -> movie.releaseYear() == 2000)
.findAny();
m1.ifPresent(System.out::println);
System.out.println("---------------------");
Optional<Movie> m2 = MOVIES.stream()
.filter(movie -> movie.releaseYear() == 2000)
.findFirst();
m2.ifPresent(System.out::println);
System.out.println("========================================");
Optional<Movie> pm1 = MOVIES.parallelStream()
.filter(movie -> movie.releaseYear() == 2000)
.findAny();
pm1.ifPresent(System.out::println);
System.out.println("---------------------");
Optional<Movie> pm2 = MOVIES.parallelStream()
.filter(movie -> movie.releaseYear() == 2000)
.findFirst();
pm2.ifPresent(System.out::println);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters){ }
}
Using allMatch(), anyMatch(), and noneMatch()
See the demo here
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 9, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
//allMatch, noneMatch, anyMatch
boolean result1 = MOVIES.stream()
.allMatch(movie -> movie.rating() == 10);
boolean result2 = MOVIES.stream()
.anyMatch(movie -> movie.rating() == 10);
boolean result3 = MOVIES.stream()
.noneMatch(movie -> movie.rating() == 8);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters){ }
}
Using takeWhile() and dropWhile()
See the demo here
They make a Stream more like a “stream” where take/drop the elements while a condition is true.
public class StreamDemo {
static List<Movie> MOVIES = List.of(
new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
new Movie(3, "Matrix", "Wachowski", "Neo", 9, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
);
public static void main(String[] args) {
MOVIES.stream()
.takeWhile(movie -> movie.rating() == 10)
.forEach(System.out::println);
System.out.println("-----------------------");
MOVIES.stream()
.dropWhile(movie -> movie.rating() == 10)
.forEach(System.out::println);
}
record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters){ }
}
Using joining()
See the demo here
public static void main(String[] args) {
String titles = MOVIES.stream()
.filter(movie -> movie.rating() == 10)
.map(Movie::title)
.collect(Collectors.joining(", "));
System.out.println(titles);
}
Output:
LOTR, The Dark Knight
Using Collectors.filtering() and Collectors.mapping()
See the demo here
String titles = MOVIES.stream()
//.filter(movie -> movie.rating() == 10)
//.map(Movie::title)
.collect(
Collectors.filtering(movie -> movie.rating() == 10,
Collectors.mapping(Movie::title,
Collectors.joining(", "))
));
System.out.println(titles);
Using toList(), toSet(), toCollection()
See the demo here
List<String> titles = MOVIES.stream()
.filter(movie -> movie.rating() == 10)
.map(Movie::title)
.collect(Collectors.toList());
Set<String> titlesSet = MOVIES.stream()
.filter(movie -> movie.rating() == 10)
.map(Movie::title)
.collect(Collectors.toSet());
ArrayList<String> titlesArrayList = MOVIES.stream()
.filter(movie -> movie.rating() == 10)
.map(Movie::title)
.collect(Collectors.toCollection(ArrayList::new));
//specifically asks to create an ArrayList
Using toMap()
See the demo here
//If keys are unique
Map<Integer, String> map = MOVIES.stream()
.collect(Collectors.toMap(Movie::rating, Movie::title));
//If not, provide a merge function to merge two values with the same key
Map<Integer, String> map1 = MOVIES.stream()
.collect(Collectors.toMap(Movie::rating,
Movie::title,
(s, s2) -> s.concat(", ").concat(s2)));
Using flatMapping()
See the demo here
List<String> titles = MOVIES.stream()
//.flatMap(movie -> Arrays.stream(movie.sideCharacters()))
.collect(
Collectors.flatMapping(movie -> Arrays.stream(movie.sideCharacters()),
Collectors.toList())
);
System.out.println(titles);
Using Collectors.groupingBy()
See the demo here
Map<Integer, List<Movie>> byRating = MOVIES.stream()
.collect(Collectors.groupingBy(Movie::rating));
System.out.println(byRating.getClass().getName());
Map<Integer, Set<Movie>> setByRating = MOVIES.stream()
.collect(Collectors.groupingBy(Movie::rating, Collectors.toSet()));
System.out.println(setByRating.getClass().getName());
Map<Integer, Set<String>> titlesByRating = MOVIES.stream()
.collect(Collectors.groupingBy(Movie::rating, Collectors.mapping(Movie::title, Collectors.toSet())));
System.out.println(titlesByRating.getClass().getName());
Map<Integer, Set<Movie>> linkedHashmapByRating = MOVIES.stream()
.collect(Collectors.groupingBy(Movie::rating, LinkedHashMap::new, Collectors.toSet()));
System.out.println(linkedHashmapByRating.getClass().getName());
Using Collectors.partitioningBy()
See the demo here
Map<Boolean, List<Movie>> moviesByRating = MOVIES.stream()
.collect(Collectors.partitioningBy(movie -> movie.rating() == 10));
System.out.println(moviesByRating);
Map<Boolean, Set<Movie>> setmoviesByRating = MOVIES.stream()
.collect(Collectors.partitioningBy(movie -> movie.rating() == 10, Collectors.toSet()));
System.out.println(setmoviesByRating);
Posted on March 14, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 2, 2024