From Imperative to Declarative in Java

ABDULCELIL CERCENAZI
2 min readMar 11, 2021

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

--

--