Java 8 : Imperative vs Declarative style of coding

The focus of this article is to compare the imperative style of solving a problem vs declarative style using a couple of examples.It assumes basic knowledge about Lambdas and Streams introduced in Java 8.

Example 1:   I do not wish to give you the problem statement on purpose. Look at the code below and try to figure out the intention of the code.


List<Player> validPlayers = new ArrayList<>();
FootballLeague premierLeague = null;
for(FootballLeague league:leagues)
{
if(league.getLeagueName() == LeagueName.PREMIER_LEAGUE )
{
premierLeague = league;
break;
}
}
if(null != premierLeague)
{
List<Player> players = premierLeague.getPlayers();
for(Player player: players)
{
if(player.getAge() <= 25)
{
validPlayers.add(player);
}
}
}
Collections.sort(validPlayers, new Comparator<Player>() {
@Override
public int compare(Player p1, Player p2 ) {
return p1.getName().compareTo(p2.getName());
}
});
StringBuilder builder = new StringBuilder();
for(Player player : validPlayers)
{
builder.append( player.getName()+ ",");
}
return builder.substring(0,builder.length()-1);

I am sure you read that code very carefully and possibly a couple of times. What does it do? You might read it again ! Most developers have a short memory. You know that you might have to revisit this code at some point and hence you will probably add comments in the code above like sort by name , remove last comma etc.

From a list of football leagues, the code checks if the league is the English Premier league, selects all the players who play for the English premier league where their age is <= 25 years. It then sorts them by their names and then finally returns a comma separated list of their names.

So our problem statement:  Given a list of football leagues, select all the players who play for the English Premier League and are <=25 years of age. Then sort their names and return them as comma separated.  Let us break this problem statement about what we want to do:

  1. Select all the players who play in the Premier league
  2. Remove all the players who are above 25 years of age
  3. Sort the names of the players
  4. Get their name and make them comma separated

Let us code this now using declarative style.

Declarative style:


return leagues.stream()
.filter(league -> league.getLeagueName() == LeagueName.PREMIER_LEAGUE)
.flatMap(league ->league.getPlayers().stream())
.filter(player -> player.getAge() <=25)
.sorted(Comparator.comparing(Player::getName))
.map(player ->player.getName())
.collect(Collectors.joining(","));

Look at the code above and the problem statements from 1-4. Don’t you think that the code above reads more like the problem statement? Not only does it read like the problem statement, the code does not have any garbage variables, no for loops instructing how we want to iterate and select. We are focusing more on what we want rather how we want to do it. Hence it is also easier to make any changes to the code in case the requirements change which means the maintenance is easy.  

We could have used a .sorted(Comparator.naturalOrder()) above and done the mapping to get the name first before sorting.


return leagues.stream()
.filter(league -> league.getLeagueName() == LeagueName.PREMIER_LEAGUE)
.flatMap(league ->league.getPlayers().stream())
.filter(player -> player.getAge() <=25)
.map(player ->player.getName())
.sorted(Comparator.naturalOrder())
.collect(Collectors.joining(","));

Example 2:  Given a list of strings,we need to output the strings based on their length. If the input string is “I”, ”ate”, ”food”, ”slept”. The output should be

1 – I

3-ate

4-food,slept

Imperative style:


Map<Integer, List<String>> lengthMap = new HashMap<>();
for(String string : strings)
{
List<String> sameLength = null;
Integer length = string.length();
if(lengthMap.get(length) == null)
{
sameLength = new ArrayList<>();
sameLength.add(string);
lengthMap.put(length, sameLength);
}else
{
List<String> sameLengthString = lengthMap.get(length);
sameLengthString.add(string);
}
}
return lengthMap;

Hmm, one thing is clear that we need a Hashmap. The key will be the length of string and the value will be the actual string. So what are we doing here

  1. Create a new hashmap
  2. Iterate through the list of strings
  3. Create  a new list
  4. If the current string has length x and x is already in the map then get the list corresponding to the length and add the current string to that list, else
  5. Create a new list and add the length of string and the string length as key , value as actual string.
  6. Done !

In short, we are grouping strings by their length.

Declarative style:


return strings.stream()
.collect(Collectors.groupingBy(String::length)));

What are we doing here:

  1. Group all the strings by length
  2. Done !

Again, the code is expressive and it translates to the problem statement very clearly !

The Collectors.groupingBy method returns a Map of Integer, List<String> or K, V where

K- the type that we are grouping by

V – The elements that we are operating on ( in this case, String)

Note that we have not considered the case where strings can repeat. This can be solved easily by using a Set instead of a List which would be pretty easy in both imperative and declarative style.

Declarative style does make your code more readable and concise.Declarative code might not be the magical solution to all problems, if you find the declarative style getting too complicated, stick to the old imperative style of programming.