A Glimpse of Functional Programming with Java

Javier Gonzalez
Javarevisited

--

Starting with Java 8, we can enhance our Java code solutions with functional approaches. But. what exactly does Java provide? Let’s take a look.

What is functional programming?

Functional programming is a programming paradigm where the focus is on “what to solve” in contrast to a procedural paradigm where the main focus is “how to solve.” In other words, functional programming is a declarative paradigm. The core of Functional Programming is to solve computational problems as an evaluation of mathematical functions. Yes, functions, these relationships associate elements of a set X (inputs) to a single element of a set Y (output). In functional programming, functions are everywhere. Functions can be stored in a variable. Functions can be passed as a parameter. Functions can receive functions as parameters or even return a function as results. Moreover, functions can be expressions (the instructions inside methods, such as 2+2, which is an arithmetic expression) 👀.

Functions as Methods

Wait, what we call methods in Java are called functions in other languages, such as in C. Are they functions in the context of functional programming with Java? Yes, they are. But they are not the only ones.

Functions as Objects

We mention store in a variable, pass as a parameter, return as a result. Only variables and objects can be stored, passed, and returned. Are functions represented as objects in Java? Yes, they are. We have what is called Functional Interfaces. We implement classes from Functional Interfaces. Then, we can create objects. These objects are called First-Class Functions. Why? It is said that it is because functions are finally being treated as first-class citizens 🙂. When encapsulated in an object, they can finally be stored, returned, and used as parameters to other functions.

Functions as Expressions

Further, we mention that functions can be expressions; therefore, they can exist inside methods, as the expressions that we create combining operators and values, right? Yes, they can. We will need a new kind of expression (that I will explain later) called Lambda Expressions. They are functions too.

Let’s review the implementation — Functional Interfaces, High-Order Functions, and Lambda Expressions.

Functional Interfaces

Functional interfaces are also called Single Abstract Method Interfaces. As the name suggests, they are interfaces that permit exactly one abstract (unimplemented) method inside them. Functional interfaces can be implemented, and the implemented class can be used to create an object that represents a function — remember, they are called First-Class Functions. This allows the language to support passing functions as arguments to other functions, returning them as the values from other functions and assigning them to variables or storing them in data structures.

The Java API contains a set of functional interfaces designed for commonly occuring use cases, so you don’t have to create your own functional interfaces for every little use case. They have located in the java.util.function package and include: Function, Predicate, UnaryOperator, BinaryOperator, Supplier, Consumer, Comparator, etc. Let’s take a look at two of them to get the idea.

java.util.function.Function

The Function interface represents a function that takes a single parameter and returns a single value. The function name is apply(), and the type of the parameter is a parameterized type. Here is an example of implementing Function and making the apply() method to take an Integer as a parameter and returns an Integer too. Figure 1 shows an example.

Figure 1. PlusOne.java — example of java.util.function.Function interface implementation

java.util.function. Predicate

The Predicate interface represents a simple function that takes a single value as parameter and returns true or false. The function name is test(). You can implement the Predicate interface using a class, like the one in Figure 2.

Figure 2. AreHappy.java — example of java.util.function.Predicate interface implementation

Then we can use these implementations to create instances of our classes PlusOne and AreHappy, as shown in Figure 3.

Figure 3. Main.java — example of Function and Predicate interface usage

At this point, you have the idea: the interface encapsulates a method to be implemented in a class; from the class, we can create objects, then the objects can be stored, passed as parameters, or returned. Please do not despair, this is a building block, we will get to the core next.

High-Order Functions

Higher-Order Functions are either functions that: (1) takes one or more Functions as parameters; or (2) Functions that return a Function as a result. For example, The Collections.sort() method takes a Comparator as a parameter. Yes, that Comparator is a Function — a method encapsulated in a class implementing an interface with only one (unimplemented) method 🙂. Take a look at the code in figure 4.

Figure 4. MyComparator.java — to use it as a parameter for Collection.sort() method

Notice that the first parameter for Collection.sort() is a List, and the second parameter is a function. The second parameter is what makes Collections.sort() a higher-order function. A little bit more exciting but still does not look like functional programming.

Lambda Expressions

Have you noticed how many times in the examples above we have created a class just to host a method (function)? A function that we are using only one time!

A lambda expression is a short block of code used as a replacement for a method declaration. It takes in parameters and returns a value. But, it doesn’t need a name and it can be implemented right inside the body of another method. Further, a lambda expression can be used to implement a single method directly from a Functional Interface. A lambda expression looks like this:

(parameters inside parenthesis) -> {body inside curly brackets}

So, we could just delete the code in Figure 1 and Figure 2 and rewrite Figure 3 as shown in Figure 5. As you can notice, the Function interface implementation is now inlined in the declaration of the plusOne lambda variable rather than in a separate class. This is shorter. So, does one expression (a line) replace a new class creation (with its implements instruction), and the override of a method with a body? Yes, that is right.

Figure 5. Main.java — Lambda Expression replacing the explicit implementation of Functional Interfaces

It is worthy to extend our definition for higher-order functions and state that a higher-order function can be a function that takes one or more lambda expressions as parameters or returns a lambda expression. For example, in Collections.sort(), the first parameter is still a List and the second parameter is a lambda expression.

Connecting the Dots: the Stream API

One of the parts of Java that better takes advantage of functional programming features is the Stream API, which provides a functional approach to processing collections of objects. The Stream API is not related to the packages InputStream or OutputStream — the InputStream and OutputStream are related to streams of bytes. Take a look at the examples in Figure 6. They show how to:

  1. Create a stream of elements — method of() and items as parameters.
  2. Add elements directly to a stream using a Fluent-Builder style.
  3. Create a stream from a collection that we already have.
  4. Perform an operation over the elements of a stream — the same operation over every single element. Thus N elements create N results. Notice the map() methods in the example.
  5. Create a stream pipeline — a chain of stream source, intermediate operations, and a terminal operation. Notice the combination of map() and sort() methods in the example.
  6. Reduced a stream, i.e., obtain a value by somehow combining (using) all elements in the stream. Thus N elements create 1 result. Notice the reduce() method in the example.
  7. Apply parallelization. Java 8 introduces a way of accomplishing parallelism in a functional style. The Stream API allows creating parallel streams, which perform operations in a parallel mode. Notice the parallelStream() method.
Figure 6. StreamsExamples.java —Examples taking advantage of Functional Programming approaches

Notice that a stream can iterate its elements itself. There is no need for a Java Iterator, or a for-each loop, or an explicit implementation of any kind of iteration. Also, notice the strong use of the Fluent-Builder Design Pattern — all those object.foo().bar().more()

What can we do with the elements described above? How to combine them with Object-Oriented elements? For instance, for something such as reading data from files, store the data in a data structure (a collection), do some sorting or searching, iterate the data for printing, calculate some statistics, etc. Let’s review that in another story.

--

--

Javier Gonzalez
Javarevisited

Assistant Professor @ California State | Former Software Engineer | ACM Distinguished Speaker | Research: Intelligent Systems, HCI, Emotion AI, CS Education.