An Introduction To Functional Programming in Java

Back-end

INtroduction to functional programming

Developers these days are mostly familiar with writing code in an imperative (procedural) approach which is simply a way of writing code by describing in exact details the steps taken by the computer to accomplish the goal.

While this might be relatively easier to learn, it’s more error-prone as the code quickly becomes very extensive. As a result optimization and extension might also become a challenge in the future.

Most mainstream languages, including object-oriented programming (OOP) languages such as Java, C++, and Python were primarily designed to support an imperative paradigm but as time passes by the Functional paradigm is slowly being introduced into these languages. 

Functional programming has become a really hot topic, not only in the Java ecosystem but also in many popular programming languages such as JavaScript and Python. This day’s most developers are gradually embracing this style of programming which is mainly declarative, meaning you focus more on "what" is to be done and less on "how" it’s going to be done and one of the most interesting things about functional programming is the fact that it easily allows us to be highly expressive and quickly express intent making it easier for everyone to easily understand our code.

Also, Functional code tends to be more concise, more predictable, and easier to test than imperative or object-oriented code.

As a beginner, most of the terminologies being used in the context of functional programming might be quite overwhelming but don’t worry everything will be gradually explained throughout this post.

Functional programming contains the following key concepts: 

  • Functions as first-class objects instead of instances of structures or classes.
  • Pure functions
  • Higher-order functions

Pure functional programming has a set of rules to follow as well: 

  • No state
  • No side effects
  • Immutable variables
  • Favor recursion over looping.

 

Functions as First-Class Objects

In the functional programming paradigm, functions are first-class objects in the language. Functions are treated like any other variable. For example, a function can be passed as an argument to other functions, can be returned by another function, and can be assigned as a value to a variable.

In Java, methods are not first-class objects. The closest we get is Java Lambda Expressions which we are going to see as we proceed.

Pure Functions

A pure function is one in which its output is derived solely from its inputs, with no side effects. I know this might not make sense to you right now, so let’s break it down.

Let’s consider an example of a pure Function (Method) in Java.

public class SampleWithPureFunction{

    public int add(int a, int b) {
        return a + b;
    }
}
Java

Notice how the return value of the add function only depends on the input parameters. This clarifies the first part “A pure function is one in which its output is derived solely from its inputs

Let’s consider an example of a non-pure Function

public class SampleWithNonPureFunction{
    private int score = 0;
 
    public int updateScore(int newPoint) {
        this.score += newPoint;
        return this.score;
    }
}
Java

Here we see how the updateScore function (method) uses a member variable (score) to calculate its return value; this breaks the first part of our definition thus making this function a non-pure function.

Apart from that it also breaks the second part of the definition which stipulates that a “pure function should have no side effect” this is merely because the updateScore method modifies the state of the score member variable while adding a new point to it, leaving behind a side effect. Moreover, we can say the score variable is mutable since the state of the variable is being changed, and as we saw before functional programming favors Immutability as it makes it easier to avoid side effects.

Another important rule regarding pure functions is – It favors recursion over looping.

Most often we use repetitive structures for looping but with functional programming we favor recursion. Recursion uses function calls to achieve looping, this technique allows us to remove some side effects that we perform while writing repetitive structures so the code becomes more expressive and readable.

Another alternative to loops is the Java Streams API which we are going to look at very soon. This API is functionally inspired.

Higher-Order Functions

A function is considered a higher-order function if at least one of the following conditions are met: 

  • The function takes one or more functions as an argument.
  • The function returns another function as a result.

The closest to higher-order functions in Java are Lambda Expression and built-in functional interfaces like Function, Consumer, Predicate, Supplier, and more under the java.util.function package.

Let’s consider an example:

public class Main {
     public static void main(String[] args) {
         List<String> listOfFruit = new ArrayList<>();
         listOfFruit.add("Mango");
         listOfFruit.add("Apple");
         listOfFruit.add("Banana");
 
         Collections.sort(listOfFruit, Comparator.reverseOrder());
 
         System.out.println(listOfFruit);
     }
}
Java

In this example, we create a fruit list and add to it some fruit which we then sort in descending order and print the list. Our output looks something like this [Mango, Banana, Apple]

The Collection.sort method takes two parameters. The first parameter is a list (in this case our fruit list) and the second parameter is the specified comparator which is a lambda (function). The lambda parameter (Comparator.reverseOrder()) is what makes Collections.sort a higher-order function as stated in the definition.

Often, a higher-order function will fulfill both of the criteria stated above.

Thinking in functional programming

Using functional programming doesn’t mean it’s all or nothing, you can always use functional programming concepts to complement Object-oriented programming (OOP) concepts, especially in Java.

Thinking in functional programming requires you to embrace functional programming concepts as we saw above.

In traditional Object-oriented programming, most developers are familiar with the imperative (procedural) style of programming. To switch or to complement their existing style of development, they have to make a transition in their thinking and their approach to development.

To solve problems, OOP developers design class hierarchies, focus on proper encapsulation and think in terms of class contracts. The behavior and state of object types are very important. Language features, such as classes, interfaces, inheritance, and polymorphism, are provided to address these concerns.

In contrast, functional programming approaches computational problems as an exercise in the evaluation of pure functional transformations of data collections. Functional programming avoids state and mutable data and instead emphasizes the application of functions.

Java Streams API

The Stream API is used to process collections of objects. This is one of the major API introduced in Java 8 and is one of my favorites. A stream can be seen as a sequence of objects that supports various operations or methods (functions) which can be chained or nested together to produce the desired result.

A stream does not store data and, in that sense, is not a data structure. A stream is basically an abstraction.

A stream can be separated into three parts namely:

  • The stream source which can be as simple as a List.
  • A sequence(s) of intermediate operations who are responsible for performing some operations on the data.
  • And finally, terminal operations which enable us to get the results

Before we go further, let’s see why the Java Stream API was required. Suppose we want to iterate over a list of integers and find the square of the first odd number greater than 3.

Prior to Java 8, the approach to do it would be our usual imperative style which goes as follows:

package imperative;
import java.util.Arrays;
import java.util.List;
 
public class Main {
     public static void main(String[] args) {
         List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
         int square = 0;
         for (Integer value : numbers) {
             if (value % 2 != 0 && value > 3) {
                 square = value * value;
                 break;
             }
         }
         System.out.println(square);
     }
 }
Java

We have a list called numbers using the java enhanced for loop we go on by saying for each value in our list (numbers) check if that value is odd (value % 2 != 0) and if it’s greater than 3 (value > 3). Finally, we square that value and we “break” to stop the iteration once the first value is gotten.

The problem in this code begins at the level of the for loop (which is not necessarily part of the problem statement but simply a mechanism). You see clearly that here we have to specify how everything has to be done, we focus more on the mechanism and implementation instead of the actual problem. Even though we manage to solve the problem there are still flaws which we need to explore, one of them being mutability which is as a result of many moving parts especially as we loop through and it’s also difficult to make the code concurrent as well. Let’s look at the problem in a more functional approach. That’s where streams come into play.

package com.bproo.agileDev;
import java.util.List;
public class Main {
     public static void main(String[] args) {
         List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
         System.out.println(
             numbers.stream()
                 .filter(value -> value % 2 != 0 && value > 3)
                 .map(value -> value * value)
                 .findFirst()
                 .get()
         );
     }
 }
Java

I know this might look confusing but wait!

The filter() function is simply used to return a new array from an old one that contains only values that fit our specified condition(s), In this case, we have two conditions

value -> value % 2 != 0 && value > 3 which means given a value, check if it is odd and check if it’s also greater than 3. Then comes the map() function.

Map is used to iterate through the items in an array, modifying each item according to the provided logic. We can say the map performs a transformation on the various items. In the above example, we transform the given value into its square by multiplying it with itself.

findFirst() returns an Optional for the first entry in the stream; the Optional can, of course, be empty.

And finally, get() as the name suggests simply gets the value.

Here we use a very expressive and declarative style of programming which reads more like our problem statements. Also functions like filter or map are very powerful as there is a lot of optimization under the hood which is not provided by traditional loops.

We can even make it more expressive by breaking down the different problem statements into separate functions thereby achieving higher-order functions.

package com.bproo.agileDev;

import java.util.List;

public class Main {

    public static boolean isOdd(int number){

        return number % 2 != 0;

    }

    public static boolean isGreaterThan3(int number){
        return number > 3;

    }

    public static void main(String[] args) {

        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);

        System.out.println(
            numbers.stream()
               .filter(Main::isOdd)
               .filter(Main::isGreaterThan3)
               .map(value -> value * value)
               .findFirst()
               .get()
        );
    }

}

Java

Other Benefits And Lazy Evaluation.

One of the most important characteristics of streams is that they allow for significant optimizations through lazy evaluations. 

Computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed.

All intermediate operations are lazy, so they’re not executed until a result of processing is needed.

When we talk of intermediate operators this includes functions like map(), filter(), reduce(), etc on the other hand terminal operators include functions like findFirst(), get(), etc.

Also, Functional programming leads to more modular code which eases reusability. In addition knowing the specific functionality of each function means pinpointing bugs and writing tests should be straightforward, especially since the function outputs should be predictable.

There are many more things we can do with functional programming, many functions that we haven’t touched but I think you have the basics and can move forward. Remember it will take some time to get used to this approach of programming so keep learning!

You can share this post!

bproo user profil

Kamdjou Duplex

the world would not be so beautiful without people willing to share knowledge