Functional programming for Java developers, Part 2

Rewrite object-oriented code using functional techniques. Get started with lambdas, method references, functional interfaces, and the Streams API in Java

1 2 3 Page 2
Page 2 of 3

Listing 4 turns ifThenElse() into a higher-order function by declaring this method to receive a pair of Function arguments. Although these arguments are eagerly evaluated when passed to ifThenElse(), the ?: operator causes only one of these functions to execute (via apply()). You can see both eager and lazy evaluation at work when you compile and run the application.

Compile Listing 4 as follows:


javac LazyEval.java

Run the resulting application as follows:


java LazyEval

You should observe the following output:


SQUARE
CUBE
in square
16
in cube
64

Closures in Java

An anonymous inner class instance is associated with a closure. Outer scope variables must be declared final or (starting in Java 8) effectively final (meaning unmodified after initialization) in order to be accessible. Consider Listing 5.

Listing 5. An example of closures in Java (PartialAdd.java)


interface Function<T, R>
{
   R apply(T t);
}
public class PartialAdd
{
   Function<Integer, Integer> add(final int x)
   {
      Function<Integer, Integer> partialAdd = new Function<Integer, Integer>()
                                              {
                                                 @Override
                                                 public Integer apply(Integer y)
                                                 {
                                                    return y + x;
                                                 }
                                              };
      return partialAdd;
   }
   public static void main(String[] args)
   {
      PartialAdd pa = new PartialAdd();
      Function<Integer, Integer> add10 = pa.add(10);
      Function<Integer, Integer> add20 = pa.add(20);
      System.out.println(add10.apply(5));
      System.out.println(add20.apply(5));
   }
}

Listing 5 is the Java equivalent of the closure I previously presented in JavaScript (see Part 1, Listing 8). This code declares an add() higher-order function that returns a function for performing partial application of the add() function. The apply() method accesses variable x in the outer scope of add(), which must be declared final prior to Java 8. The code behaves pretty much the same as the JavaScript equivalent.

Compile Listing 5 as follows:


javac PartialAdd.java

Run the resulting application as follows:


java PartialAdd

You should observe the following output:


15
25

Currying in Java

You might have noticed that the PartialAdd in Listing 5 demonstrates more than just closures. It also demonstrates currying, which is a way to translate a multi-argument function's evaluation into the evaluation of an equivalent sequence of single-argument functions. Both pa.add(10) and pa.add(20) in Listing 5 return a closure that records an operand (10 or 20, respectively) and a function that performs the addition--the second operand (5) is passed via add10.apply(5) or add20.apply(5).

Currying lets us evaluate function arguments one at a time, producing a new function with one less argument on each step. For example, in the PartialAdd application, we are currying the following function:


f(x, y) = x + y

We could apply both arguments at the same time, yielding the following:


f(10, 5) = 10 + 5

However, with currying, we apply only the first argument, yielding this:


f(10, y) = g(y) = 10 + y

We now have a single function, g, that takes only a single argument. This is the function that will be evaluated when we call the apply() method.

You might be confused by my use of the phrase "partial application," especially because I stated in Part 1 that currying isn't the same as partial application, which is the process of fixing a number of arguments to a function, producing another function of smaller arity. With partial application, you can produce functions with more than one argument, but with currying, each function must have exactly one argument.

Listing 5 presents a small example of Java-based currying prior to Java 8. Now consider the CurriedCalc application in Listing 6.

Listing 6. Currying in Java code (CurriedCalc.java)


interface Function<T, R>
{
   R apply(T t);
}
public class CurriedCalc
{
   public static void main(String[] args)
   {
      System.out.println(calc(1).apply(2).apply(3).apply(4));
   }
   static Function<Integer, Function<Integer, Function<Integer, Integer>>> 
      calc(final Integer a)
   {
      return new Function<Integer, 
                          Function<Integer, Function<Integer, Integer>>>()
             {
                @Override
                public Function<Integer, Function<Integer, Integer>> 
                   apply(final Integer b)
                {
                   return new Function<Integer, Function<Integer, Integer>>()
                          {
                             @Override
                             public Function<Integer, Integer> 
                                apply(final Integer c)
                             {
                                return new Function<Integer, Integer>()
                                {
                                   @Override
                                   public Integer apply(Integer d)
                                   {
                                      return (a + b) * (c + d);
                                   }
                                };
                             }
                          };
                }
             };
   }
}

Listing 6 uses currying to evaluate the function f(a, b, c, d) = (a + b) * (c + d). Given expression calc(1).apply(2).apply(3).apply(4), this function is curried as follows:

  1. f(1, b, c, d) = g(b, c, d) = (1 + b) * (c + d)
  2. g(2, c, d) = h(c, d) = (1 + 2) * (c + d)
  3. h(3, d) = i(d) = (1 + 2) * (3 + d)
  4. i(4) = (1 + 2) * (3 + 4)

Compile Listing 6:


javac CurriedCalc.java

Run the resulting application:


java CurriedCalc

You should observe the following output:


21

Because currying is about performing partial application of a function, it doesn't matter in what order the arguments are applied. For example, instead of passing a to calc() and d to the most-nested apply() method (which performs the calculation), we could reverse these parameter names. This would result in d c b a instead of a b c d, but it would still achieve the same result of 21. (The source code for this tutorial includes the alternative version of CurriedCalc.)

Functional programming in Java 8

Functional programming before Java 8 isn't pretty. Too much code is required to create, pass a function to, and/or return a function from a first-class function. Prior versions of Java also lack predefined functional interfaces and first-class functions such as filter and map.

Java 8 reduces verbosity largely by introducing lambdas and method references to the Java language. It also offers predefined functional interfaces, and it makes filter, map, reduce, and other reusable first-class functions available via the Streams API.

We'll look at these improvements together in the next sections.

Writing lambdas in Java code

A lambda is an expression that describes a function by denoting an implementation of a functional interface. Here's an example:


() -> System.out.println("my first lambda")

From left to right, () identifies the lambda's formal parameter list (there are no parameters), -> signifies a lambda expression, and System.out.println("my first lambda") is the lambda's body (the code to be executed).

A lambda has a type, which is any functional interface for which the lambda is an implementation. One such type is java.lang.Runnable, because Runnable's void run() method also has an empty formal parameter list:


Runnable r = () -> System.out.println("my first lambda");

You can pass the lambda anywhere that a Runnable argument is required; for example, the Thread(Runnable r) constructor. Assuming that the previous assignment has occurred, you could pass r to this constructor, as follows:


new Thread(r);

Alternatively, you could pass the lambda directly to the constructor:


new Thread(() -> System.out.println("my first lambda"));

This is definitely more compact than the pre-Java 8 version:


new Thread(new Runnable()
           {
              @Override
              public void run()
              {
                 System.out.println("my first lambda");
              }
           });

A lambda-based file filter

My previous demonstration of higher-order functions presented a file filter based on an anonymous inner class. Here's the lambda-based equivalent:


File[] txtFiles = new File(".").listFiles(p -> p.getAbsolutePath().endsWith("txt"));

Return statements in lambda expressions

In Part 1, I mentioned that functional programming languages work with expressions as opposed to statements. Prior to Java 8, you could largely eliminate statements in functional programming, but you couldn't eliminate the return statement.

The above code fragment shows that a lambda doesn't require a return statement to return a value (a Boolean true/false value, in this case): you just specify the expression without return [and add] a semicolon. However, for multi-statement lambdas, you'll still need the return statement. In these cases you must place the lambda's body between braces as follows (don't forget the semicolon to terminate the statement):


File[] txtFiles = new File(".").listFiles(p -> { return p.getAbsolutePath().endsWith("txt"); });

Lambdas with functional interfaces

I have two more examples to illustrate the conciseness of lambdas. First, let's revisit the main() method from the Sort application shown in Listing 2:


public static void main(String[] args)
{
   String[] innerplanets = { "Mercury", "Venus", "Earth", "Mars" };
   dump(innerplanets);
   sort(innerplanets, (e1, e2) -> e1.compareTo(e2));
   dump(innerplanets);
   sort(innerplanets, (e1, e2) -> e2.compareTo(e1));
   dump(innerplanets);
}

We can also update the calc() method from the CurriedCalc application shown in Listing 6:


static Function<Integer, Function<Integer, Function<Integer, Integer>>> 
   calc(Integer a)
{
   return b -> c -> d -> (a + b) * (c + d);
}

Runnable, FileFilter, and Comparator are examples of functional interfaces, which describe functions. Java 8 formalized this concept by requiring a functional interface to be annotated with the java.lang.FunctionalInterface annotation type, as in @FunctionalInterface. An interface that is annotated with this type must declare exactly one abstract method.

You can use Java's pre-defined functional interfaces (discussed later), or you can easily specify your own, as follows:


@FunctionalInterface
interface Function<T, R>
{
   R apply(T t);
}

You might then use this functional interface as shown here:


public static void main(String[] args)
{
   System.out.println(getValue(t -> (int) (Math.random() * t), 10));
   System.out.println(getValue(x -> x * x, 20));
}
static Integer getValue(Function<Integer, Integer> f, int x)
{
   return f.apply(x);
}

Method references in Java

Some lambdas only invoke an existing method. For example, the following lambda invokes System.out's void println(s) method on the lambda's single argument:


(String s) -> System.out.println(s)

The lambda presents (String s) as its formal parameter list and a code body whose System.out.println(s) expression prints s's value to the standard output stream.

To save keystrokes, you could replace the lambda with a method reference, which is a compact reference to an existing method. For example, you could replace the previous code fragment with the following:


System.out::println

Here, :: signifies that System.out's void println(String s) method is being referenced. The method reference results in much shorter code than we achieved with the previous lambda.

A method reference for Sort

I previously showed a lambda version of the Sort application from Listing 2. Here is that same code written with a method reference instead:


public static void main(String[] args)
{
   String[] innerplanets = { "Mercury", "Venus", "Earth", "Mars" };
   dump(innerplanets);
   sort(innerplanets, String::compareTo);
   dump(innerplanets);
   sort(innerplanets, Comparator.comparing(String::toString).reversed());
   dump(innerplanets);
}
1 2 3 Page 2
Page 2 of 3