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 3
Page 3 of 3

The String::compareTo method reference version is shorter than the lambda version of (e1, e2) -> e1.compareTo(e2). Note, however, that a longer expression is required to create an equivalent reverse-order sort, which also includes a method reference: String::toString. Instead of specifying String::toString, I could have specified the equivalent s -> s.toString() lambda.

Predefined functional interfaces

Java 8 introduced predefined functional interfaces (java.util.function) so that developers don't have create our own functional interfaces for common tasks. Here are a few examples:

  • The Consumer<T> functional interface represents an operation that accepts a single input argument and returns no result. Its void accept(T t) method performs this operation on argument t.
  • The Function<T, R> functional interface represents a function that accepts one argument and returns a result. Its R apply(T t) method applies this function to argument t and returns the result.
  • The Predicate<T> functional interface represents a predicate (Boolean-valued function) of one argument. Its boolean test(T t) method evaluates this predicate on argument t and returns true or false.
  • The Supplier<T> functional interface represents a supplier of results. Its T get() method receives no argument(s) but returns a result.

The DaysInMonth application in Listing 1 revealed a complete Function interface. Starting with Java 8, you can remove this interface and import the identical predefined Function interface.

Functional APIs: Streams

Java 8 introduced the Streams API to facilitate sequential and parallel processing of data items. This API is based on streams, where a stream is a sequence of elements originating from a source and supporting sequential and parallel aggregate operations. A source stores elements (such as a collection) or generates elements (such as a random number generator). An aggregate is a result calculated from multiple input values.

A stream supports intermediate and terminal operations. An intermediate operation returns a new stream, whereas a terminal operation consumes the stream. Operations are connected into a pipeline (via method chaining). The pipeline starts with a source, which is followed by zero or more intermediate operations, and ends with a terminal operation.

Streams is an example of a functional API. It offers filter, map, reduce, and other reusable first-class functions. I briefly demonstrated this API in the Employees application shown in Part 1, Listing 1. Listing 7 offers another example.

Listing 7. Functional programming with Streams (StreamFP.java)


import java.util.Random;
import java.util.stream.IntStream;
public class StreamFP
{
   public static void main(String[] args)
   {
      new Random().ints(0, 11).limit(10).filter(x -> x % 2 == 0)
                  .forEach(System.out::println);
      System.out.println();
      String[] cities = 
      {
         "New York",
         "London",
         "Paris",
         "Berlin",
         "BrasÌlia",
         "Tokyo",
         "Beijing",
         "Jerusalem",
         "Cairo",
         "Riyadh",
         "Moscow"
      };
      IntStream.range(0, 11).mapToObj(i -> cities[i])
               .forEach(System.out::println);
      System.out.println();
      System.out.println(IntStream.range(0, 10).reduce(0, (x, y) -> x + y));
      System.out.println(IntStream.range(0, 10).reduce(0, Integer::sum));
   }
}

The main() method first creates a stream of pseudorandom integers starting at 0 and ending at 10. The stream is limited to exactly 10 integers. The filter() first-class function receives a lambda as its predicate argument. The predicate removes odd integers from the stream. Finally, the forEach() first-class function prints each even integer to the standard output via the System.out::println method reference.

The main() method next creates an integer stream that produces a sequential range of integers starting at 0 and ending at 10. The mapToObj() first-class function receives a lambda that maps an integer to the equivalent string at the integer index in the cities array. The city name is then sent to the standard output via the forEach() first-class function and its System.out::println method reference.

Lastly, main() demonstrates the reduce() first-class function. An integer stream that produces the same range of integers as in the previous example is reduced to a sum of their values, which is subsequently output.

Compile Listing 7 as follows:


javac StreamFP.java

Run the resulting application as follows:


java StreamFP

I observed the following output from one run:


0
2
10
6
0
8
10
New York
London
Paris
Berlin
BrasÌlia
Tokyo
Beijing
Jerusalem
Cairo
Riyadh
Moscow
45
45

You might have expected 10 instead of 7 pseudorandom even integers (ranging from 0 through 10, thanks to range(0, 11)) to appear at the beginning of the output. After all, limit(10) seems to indicate that 10 integers will be output. However, this isn't the case. Although the limit(10) call results in a stream of exactly 10 integers, the filter(x -> x % 2 == 0) call results in odd integers being removed from the stream.

In conclusion

Many Java developers won't pursue pure functional programming in a language like Haskell because it differs so greatly from the familiar imperative, object-oriented paradigm. Java 8's functional programming capabilities are designed to bridge that gap, enabling Java developers to write code that's easier to understand, maintain, and test. Functional code is also more reusable and more suitable for parallel processing in Java. With all of these incentives, there's really no reason not to incorporate Java's functional programming options into your Java code.

Sidebar: Write a functional Bubble Sort application

Functional thinking is a term coined by Neal Ford, which refers to the cognitive shift from the object-oriented paradigm to the functional programming paradigm. As you've seen in this tutorial, it's possible to learn a lot about functional programming by rewriting object-oriented code using functional techniques.

Cap off what you've learned so far by revisiting the Sort application from Listing 2. In this quick sidebar, I'll show you how to write a purely functional Bubble Sort, first using pre-Java 8 techniques, and then using Java 8's functional features.

1 2 3 Page 3
Page 3 of 3