From Imperative to Declarative in Java
What makes this possible?
Short answer: Streams
What are streams?
Streams were introduced in Java 8. According to Oracle docs, it’s:
Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections
Let’s put it in more practical terms, a stream consists of a data source, followed by zero or more intermediate operations and then a terminal operation.
What data sources?
- Collections, Lists, Sets, ints, longs, doubles, arrays, lines of a file.
what intermediate operations?
- filter, map, sort, etc.
- Those operations return a stream, so they can be chained with other operations.
What terminal operations?
- forEach, collect, reduce, findFirst, etc.
- They return a void or non-stream result.
- If a stream doesn’t have a terminal operation, the intermediate ones aren’t going to get called.
Let’s convert a function from imperative to a declarative style
The imperative function
private int getResult_imperative(List<String> strings) {
int result = 0;
for (String string : strings){
if(isDigit(string.charAt(0))) continue;
if (string.contains("_")) continue;
result += string.length();
}
return result;
}
we notice that we manually have to do a couple of things
- declare a result variable to keep track of the result
- loop through the Strings
- write two if statements (which could be much more complex than this case)
- add the length of each one to the result
Let’s check the declarative style
private int getResult_Declarative(List<String> strings){
return strings.
stream().
filter(s -> !isDigit(s.charAt(0))).
filter(s -> !s.contains("_")).
mapToInt(String::length).
sum();
}
so, what is different?
- we get a Stream object by calling the stream() function
- (Intermediate operation) we apply to the filter function twice, for each time we specify a condition that only the elements we want to proceed to the next phase should meet.
- (Intermediate operation) we map each String object to an int by calling the length method (using method reference style)
- (terminal operation) we sum all the previous int values
Observations
Didn’t the second approach feel more straightforward? We specified what we wanted and not how we want to do it. That is the spirit of declarative programming and the purpose of the Stream API in modern Java applications.
An excellent source to get more in-depth regarding streams in Java.
https://www.youtube.com/watch?v=1OpAgZvYXLQ&ab_channel=Devoxx