Java 101: Functional programming for Java developers, Part 1

Optimize your Java code with these five functional programming techniques

Java 8 introduced Java developers to functional programming with lambda expressions. This Java release effectively notified developers that it's no longer sufficient to think about Java programming only from the imperative, object-oriented perspective. A Java developer must also be able to think and code using the declarative functional paradigm.

This tutorial presents the basics of functional programming. I'll start with terminology, then we'll dig into functional programming concepts. I will conclude by introducing you to five functional programming techniques. Code examples in these sections will get you started with pure functions, higher-order functions, lazy evaluation, closures, and currying.

What is functional programming?

Computers typically implement the Von Neumann architecture, which is a widely-used computer architecture based on a 1945 description by the mathematician and physicist John von Neumann (and others). This architecture is biased toward imperative programming, which is a programming paradigm that uses statements to change a program's state. C, C++, and Java are all imperative programming languages.

In 1977, distinguished computer scientist John Backus (notable for his work on FORTRAN), gave a lecture titled "Can programming be liberated from the von Neumann style?." Backus asserted that the Von Neumann architecture and its associated imperative languages are fundamentally flawed, and presented a functional-level programming language (FP) as a solution.

Functional programming concepts and terminology

Functional programming is a programming style in which computations are codified as functional programming functions. These are mathematical function-like constructs (e.g., lambda functions) that are evaluated in expression contexts.

Functional programming languages are declarative, meaning that a computation's logic is expressed without describing its control flow. In declarative programming, there are no statements. Instead, programmers use expressions to tell the computer what needs to be done, but not how to accomplish the task. If you are familiar with SQL or regular expressions, then you have some experience with the declarative style; both use expressions to describe what needs to be done, rather than using statements to describe how to do it.

A computation in functional programming is described by functions that are evaluated in expression contexts. These functions are not the same as the functions used in imperative programming, such as a Java method that returns a value. Instead, a functional programming function is like a mathematical function, which produces an output that typically depends only on its arguments. Each time a functional programming function is called with the same arguments, the same result is achieved. Functions in functional programming are said to exhibit referential transparency. This means you could replace a function call with its resulting value without changing the computation's meaning.

Functional programming favors immutability, which means the state cannot change. This is typically not the case in imperative programming, where an imperative function might be associated with state (such as a Java instance variable). Calling this function at different times with the same arguments might result in different return values because in this case state is mutable, meaning it changes.

Side effects in imperative and functional programming

State changes are a side effect of imperative programming, preventing referential transparency. There are many other side effects worth knowing about, especially as you evaluate whether to use the imperative or functional style in your programs.

One common side effect in imperative programming is when an assignment statement mutates a variable by changing its stored value. Functions in functional programming don't support variable assignments. Because a variable's initial value never changes, functional programming eliminates this side effect.

Another common side effect happens when modifying an imperative function's behavior based on a thrown exception, which is an observable interaction with the caller. For more information, see the Stack Overflow discussion, "Why is the raising of an exception a side effect?"

A third common side effect occurs when an I/O operation inputs text that cannot be unread, or outputs text that cannot be unwritten. See the Stack Exchange discussion "How can IO cause side effects in functional programming?" to learn more about this side effect.

Eliminating side effects makes it much easier to understand and predict computational behavior. It also helps make code more suitable for parallel processing, which often improves application performance. While there are side effects in functional programming, they are generally fewer than in imperative programming. Using functional programming can help you write code that's easier to understand, maintain, and test, and is also more reusable.

Object-oriented versus functional programming

I've created a Java application that contrasts the imperative, object-oriented and declarative, functional programming approaches to writing code. Study the code below and then I'll point out differences between the two examples.

Listing 1. Employees.java



import java.util.ArrayList;

import java.util.List;

public class Employees

{

   static class Employee

   {

      private String name;

      private int age;

      Employee(String name, int age)

      {

         this.name = name;

         this.age = age;

      }

      int getAge()

      {

         return age;

      }

      @Override

      public String toString()

      {

         return name + ": " + age;

      }

   }

   public static void main(String[] args)

   {

      List<Employee> employees = new ArrayList<>();

      employees.add(new Employee("John Doe", 63));

      employees.add(new Employee("Sally Smith", 29));

      employees.add(new Employee("Bob Jone", 36));

      employees.add(new Employee("Margaret Foster", 53));

      printEmployee1(employees, 50);

      System.out.println();

      printEmployee2(employees, 50);

   }

   public static void printEmployee1(List<Employee> employees, int age)

   {

      for (Employee emp: employees)

         if (emp.getAge() < age)

            System.out.println(emp);

   }

   public static void printEmployee2(List<Employee> employees, int age)

   {

      employees.stream()

               .filter(emp -> emp.age < age)

               .forEach(emp -> System.out.println(emp));

   }

}

Listing 1 reveals an Employees application that creates a few Employee objects, then prints a list of all employees who are younger than 50. This code demonstrates both object-oriented and functional programming styles.

The printEmployee1() method reveals the imperative, statement-oriented approach. As specified, this method iterates over a list of employees, compares each employee's age against an argument value, and (if the age is less than the argument), prints the employee's details.

The printEmployee2() method reveals the declarative, expression-oriented approach, in this case implemented with the Streams API. Instead of imperatively specifying how to print the employees (step-by-step), the expression specifies the desired outcome and leaves the details of how to do it to Java. Think of filter() as the functional equivalent of an if statement, and forEach() as functionally equivalent to the for statement.

You can compile Listing 1 as follows:



javac Employees.java

Use the following command to run the resulting application:



java Employees

The output should look something like this:



Sally Smith: 29

Bob Jone: 36

Sally Smith: 29

Bob Jone: 36

Functional programming examples

In the next sections, we'll explore five core techniques used in functional programming: pure functions, higher-order functions, lazy evaluation, closures, and currying. Examples in this section are coded in JavaScript because its simplicity, relative to Java, will allow us to focus on the techniques. In Part 2 we'll revisit these same techniques using Java code.

download
Get code samples for the five functional programming techniques demonstrated in the next sections. Created by Jeff Friesen for JavaWorld.

Listing 2 presents the source code to RunScript, a Java application that uses Java's Scripting API to facilitate running JavaScript code. RunScript will be the base program for all of the forthcoming examples.

Listing 2. RunScript.java

 
import java.io.FileReader;

import java.io.IOException;

import javax.script.ScriptEngine;

import javax.script.ScriptEngineManager;

import javax.script.ScriptException;

import static java.lang.System.*;

public class RunScript

{

   public static void main(String[] args)

   {

      if (args.length != 1)

      {

         err.println("usage: java RunScript script");

         return;

      }

      ScriptEngineManager manager = 

         new ScriptEngineManager();

      ScriptEngine engine = 

         manager.getEngineByName("nashorn");

      try

      {

         engine.eval(new FileReader(args[0]));

      }

      catch (ScriptException se)

      {

         err.println(se.getMessage());

      }

      catch (IOException ioe)

      {

         err.println(ioe.getMessage());

      }      

   }

}

The main() method in this example first verifies that a single command-line argument (the name of a script file) has been specified. Otherwise, it displays usage information and terminates the application.

Assuming the presence of this argument, main() instantiates the javax.script.ScriptEngineManager class. ScriptEngineManager is the entry-point into Java's Scripting API.

Next, the ScriptEngineManager object's ScriptEngine getEngineByName(String shortName) method is called to obtain a script engine corresponding to the desired shortName value. Java 10 supports the Nashorn script engine, which is obtained by passing "nashorn" to getEngineByName(). The returned object's class implements the javax.script.ScriptEngine interface.

ScriptEngine declares several eval() methods for evaluating a script. main() invokes the Object eval(Reader reader) method to read the script from its java.io.FileReader object argument and (assuming that java.io.IOException isn't thrown) then evaluate the script. This method returns any script return value, which I ignore. Also, this method throws javax.script.ScriptException when an error occurs in the script.

Compile Listing 2 as follows:



javac RunScript.java

I'll show you how to run this application after I have presented the first script.

Functional programming with pure functions

A pure function is a functional programming function that depends only on its input arguments and no external state. An impure function is a functional programming function that violates either of these requirements. Because pure functions have no interaction with the outside world (apart from calling other pure functions), a pure function always returns the same result for the same arguments. Pure functions also have no observable side effects.

Pure functions versus impure functions

The JavaScript in Listing 3 contrasts an impure calculatebonus() function with a pure calculatebonus2() function.

Listing 3. Comparing pure vs impure functions (script1.js)



// impure bonus calculation

var limit = 100;

function calculatebonus(numSales) 

{

   return(numSales > limit) ? 0.10 * numSales : 0

}

print(calculatebonus(174))

// pure bonus calculation

function calculatebonus2(numSales)

{

   return (numSales > 100) ? 0.10 * numSales : 0

}

print(calculatebonus2(174))

calculatebonus() is impure because it accesses the external limit variable. In contrast, calculatebonus2() is pure because it obeys both requirements for purity. Run script1.js as follows:



java RunScript script1.js

Here's the output you should observe:



17.400000000000002

17.400000000000002

1 2 Page 1
Page 1 of 2