Adopting Java Streams
Written by Andrew Lalis at 11 January, 2022
How to integrate Java 8 Streams API into your data processing.
So, Java 8 was released all the way back in March of 2014, and even to this day, as someone who teaches the Java language to university students, I see people avoiding the streams API (and also the java.time
API, but that's a story for another day).
I believe that if you start learning how to handle streams right away, you'll come to find that they lead to much simpler, more readable code, and in many cases, better performance over their plain iterative counterparts.
Let's start with a basic example: find the sum of all numbers in an array. If you've programmed in Java for at least 5 minutes, you should probably know how to pull this off.
double[] numbers = getNumbers(); // Some method to get an array of numbers.
double sum = 0.0;
for (double n : numbers) {
sum += n;
}
System.out.println(sum);
Now, let's implement that same snippet using the Streams API. We'll start with the same getNumbers()
method call.
System.out.println(DoubleStream.of(getNumbers()).sum());
getNumbers()
, and then we can just call the sum method on it.
Okay, maybe this example is a little trivial, because of course Java would add a shortcut method for finding the sum of a stream of numbers. However, let's take a look at another, more complex use case: Suppose we have a list of access tokens in our application, and we want to remove all of those which are expired. Again, we'll begin by defining a classic iterative solution.
record Token(String id, LocalDateTime expiresAt) {}
public List<Token> getAllTokens() {...}
public void deleteToken(Token t) {...}
public void deleteExpiredTokens() {
LocalDateTime now = LocalDateTime.now();
for (Token t : getAllTokens()) {
if (t.expiresAt().isBefore(now)) {
deleteToken(t);
}
}
}
And now let's try and use streams to make that method more concise.
public void deleteExpiredTokens() {
LocalDateTime now = LocalDateTime.now();
getAllTokens().stream()
.filter(t -> t.expiresAt().isBefore(now))
.forEach(t -> deleteToken(t));
}
In this example, our stream-oriented code isn't really that much shorter than the classic approach, but what it does improve is the readability. At a glance, you can clearly see that we first filter the tokens, then do something for each of the filtered tokens. Yeah, it may seem a bit silly to learn a whole new API just for some cleanup, but in large projects with many collaborators, you need to make every effort to make your code as readable as possible, and streams provide a simple way to do this.