Learning Java Functional Programming - Sample Chapter
Learning Java Functional Programming - Sample Chapter
$ 54.99 US
34.99 UK
P U B L I S H I N G
Richard M Reese
ee
Sa
m
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
Richard M Reese
Richard has written several Java books and a C pointer book. He uses a concise and
easy-to-follow approach to the topics at hand. His Java books have addressed EJB
3.1, updates to Java 7 and 8, certification, jMonkeyEngine, and Natural Language
Processing.
Preface
With the introduction of Java 8, many functional programming techniques have been
added to the language. However, functional programming may seem unfamiliar to
developers who are used to using imperative and object-oriented techniques. The
new additions to Java 8 offer the opportunity to develop more maintainable and
robust applications than that offered by earlier versions of Java.
The goal of this book is to introduce functional programming techniques to
developers who are not familiar with this technology. You will be guided through
the use of functional programming techniques with the help of numerous examples.
Older imperative and object-oriented approaches will be illustrated and contrasted
with equivalent functional programming solutions.
Preface
A mathematical function
[1]
Fluent interfaces
This is followed by the support Java 8 provides for functional programming, including:
Lambda expressions
Default methods
Functional interface
Collections
Functions
Function composition
Fluent interfaces
[2]
Chapter 1
Parallelism
Recursion
Each of these concepts will be introduced in the following sections. We will explore
the nature of each concept, explain why it is important, and when practical provide
simple examples using Java.
Functions
Functions are the foundation of functional programming languages. They play a
central role in supporting other functional programming concepts. In this section,
we will introduce many of the terms used to describe functions including high-order,
first-class, and pure functions. The concepts of closure and currying will also
be explained.
First-class and high-order functions are associated with functional programming. A
first-class function is a computer science term. It refers to functions that can be used
anywhere a first-class entity can be used. A first-class entity includes elements such
as numbers and strings. They can be used as an argument to a function, returned
from a function, or assigned to a variable.
High-order functions depend upon the existence of first-class functions. They are
functions that either:
Return a function
Java 8 has introduced the concept of lambda expressions to the language. These are
essentially anonymous functions that can be passed to and returned from functions.
They can also be assigned to a variable. The basic form of a lambda expression
follows where a parameter, such as x, is passed to the body of the function. The
lambda operator, ->, separates the parameter from the body. This function is
passed a value, which is multiplied by two and then returned, as follows:
x -> 2 * x
In this lambda expression, it is assumed that an integer is passed and that integer is
returned. However, the data type is not restricted to an integer as we will see later.
In the following lambda expression, an argument is passed and nothing is returned:
x->System.out.println(x)
[3]
Lambda expressions must be used in the proper context. It would not be appropriate
to pass a lambda expression, which returns a value to a method, to a function that
cannot use the returned value.
We can use the previous expression in many places that expect a single value being
passed and nothing to be returned as shown next. In the following example, an array
of integers is converted to a list. The lambda expression is then used as an argument
to the List class's forEach method, which displays each element of the list. The
forEach method applies the lambda expression to each element in the list, avoiding
having to create an explicit loop to achieve the same effect:
Integer arr[] = {1,2,3,4,5};
List<Integer> list = Arrays.asList(arr);
list.forEach(x->System.out.println(x));
The output will list the numbers one to five on separate lines.
Changing a program's state is avoided in functional programming. Calling a function
with the same input values should result in the same behavior each time. This makes
it easier to understand the function. Imperative programming changes the state
using statements such as the assignment statement.
A pure function is a function that has no side effects. This means that memory
external to the function is not modified, IO is not performed, and no exceptions are
thrown. With a pure function, when it is called repeatedly with the same parameters,
it will return the same value. This is called referential transparency.
With referential transparency, it is permissible to modify local variables within the
function as this does not change the state of the program. Any changes are not seen
outside of the function.
Advantages of pure function include:
The function can be called repeatedly with the same argument and get the
same results. This enables caching optimization (memorization).
Chapter 1
Pure function enables lazy evaluation as discussed later in the Strict versus
non-strict evaluation section. This implies that the execution of the function
can be delayed and its results can be cached potentially improving the
performance of a program.
If the result of a function is not used, then it can be removed since it does
not affect other operations.
There are several other terms associated with functions, such as the term closure.
This refers to a function passed around along with its environment. The environment
consists of the variables it uses. Java 8 supports a form of closure, and will be
illustrated in Chapter 2, Putting the Function in Functional Programming.
Currying is the process of evaluating multiple arguments of a function one-by-one,
producing intermediate results. In the process, we introduce a new function with
one less argument than the previous step. For example, let's start with this function:
f ( x, y ) = x + y
We reduced the number of arguments from two to one. Using a value of 4 for
y yields the original result of 7. The process of currying, and partially applying
functions, permit high-order functions to be used more effectively. This will
become clearer in Chapter 2, Putting the Function in Functional Programming.
[5]
Function composition
Imperative programming places emphasis on a step-by-step process to implement
an application. This is typified by a logical set of steps where code is executed using
basic control constructs and is often encapsulated in functions or procedures.
Functional programming places more emphasis on how these functions are arranged
and combined. It is this composition of functions, which typifies a functional style of
programming. Functions are not only used to organize the execution process, but are
also passed and returned from functions. Often data and the functions acting on the
data are passed together promoting more capable and expressive programs.
We will illustrate this technique using the Function interface as defined in the java.
util.function package. This interface possesses a compose and andThen methods.
Both of these methods return a composed function.
The compose method will execute the function passed to it first, and then uses its
result with the function the compose method is executed against. The andThen
method will execute the first function and then execute the function passed as an
argument to the andThen method.
The next code sequence demonstrates the compose method, which is passed as a
function to take the absolute value of a number. The absThenNegate variable is
assigned a function that will also negate the number. This variable is declared as a
Function type, which means that the function assigned to it expects to be passed as
an integer and returns an integer.
This function will execute the argument of the compose method and the Math class's
abs method first, against some value, and then apply the negateExact method
to this result. In other words, it will take the absolute value of a number and then
negate it. Both of these methods are expressed as method references, which are new
to Java 8. A method reference consist of the class name followed by a set of double
colons, and then a method name providing a simpler form of method invocation:
Function<Integer,Integer>absThenNegate =
((Function<Integer,Integer>)Math::negateExact)
.compose(Math::abs);
This is illustrated with the following sequence. The Function interface's apply
method is used to invoke the composed function:
System.out.println(absThenNegate.apply(-25));
System.out.println(absThenNegate.apply(25));
[6]
Chapter 1
Both of these statements will display a -25. In the first statement, the absolute value
of a -25 is obtained and then negated. The second statement works the same way
except its argument is +25.
The negateThenAbs variable that follows, illustrates the andThen method. The
function used as an argument to the andThen method is applied after the first function
is executed. In this case, the negateExact method is executed first and then the abs
function is applied:
Function<Integer,Integer>negateThenAbs =
((Function<Integer,Integer>)Math::negateExact)
.andThen(Math::abs);
System.out.println(negateThenAbs.apply(-25));
System.out.println(negateThenAbs.apply(25));
Fluent interfaces
Fluent interfaces constitute a way of composing expressions that are easier to write
and understand. A fluent interface is often implemented using method chaining,
sometimes called method cascading, where the returned value is used again in the
same context.
In Java 8, the use of fluent interfaces is found in numerous places. We will illustrate
this style with an example using the new Date and Time API.
Suppose we want to calculate a new date that is 2 years in the future, minus 1 month
plus 3 days. We can use the following code sequence to achieve this result. The
LocalDate class's method now returns an instance of the LocalDate class representing
the current date. This date is the base for creating a new day called futureDate:
LocalDate today = LocalDate.now();
LocalDate futureDate = today.plusYears(2);
futureDate = futureDate.minusMonths(1);
futureDate = futureDate.plusDays(3);
System.out.println(today);
System.out.println(futureDate);
[7]
Contrast this with the next code sequence, which takes advantage of the APIs fluent
interface and produces the same output:
LocalDatefutureDate = LocalDate.now()
.plusYears(2)
.minusMonths(1)
.plusDays(3);
The code flow is easy to read and flows in a more natural way. You will see repeated
usage of fluent interfaces in the book. Streams use this approach consistently.
[8]
Chapter 1
The ints method returns an IntStream instance. The limit method will restrict the
stream to the first five numbers, and the sorted method will sort these numbers.
However, the stream is not evaluated until a terminal method such as the forEach
method is encountered. The use of streams will be demonstrated in more detail in
Chapter 4, Streams and the Evaluation of Expressions.
One possible output follows:
-1790144043
-1777206416
23979263
801416357
874941096
A stream is processed lazily, which enables the runtime system to optimize how
the stream's component operations are executed. In addition, they are used in a
fluent manner.
[9]
We are not able to show how Java can support this concept here. However, in
Chapter 6, Optional and Monads we will examine techniques that can be used in
Java 8 to support data structures, such as some monads, in more detail.
Recursion
A loop is used in an imperative language to perform repeated calculations.
Recursion is a technique that can achieve the same effect but often in a more elegant
manner. A function is recursive if it calls itself either directly or indirectly. For
example, calculating the factorial of a number can be accomplished using either
iteration or recursion. The factorial of a number is defined as follows:
f (1) = 1
f ( n ) = n f ( n 1)
where n>0
An iterative solution follows:
int result = 1;
for (int i = 5; i>= 1; i--) {
result = result * i;
}
System.out.println(result);
The output will be 120. The equivalent recursion solution starts with a recursive
factorial function:
public int factorial(int n) {
if(n==1) {
return 1;
} else {
return n * factorial(n-1);
}
}
This solution is more succinct than the iterative version and more closely matches
the problem definition. The function can be invoked as follows:
System.out.println(factorial(num));
[ 10 ]
Chapter 1
Parallelism
One area where the use of functional programming can be useful is handling parallel,
also called concurrent, programming tasks. Consider the following sequence:
result = a.methodA() + b.methodB() + c.methodC();
In what order can these methods be executed? If they have side effects, then they
will most likely need to be computed sequentially. For example, the effect of methodA
may affect the results of the other methods. However, if they do not have side effects,
then the order of execution is not important and can be executed concurrently.
Conceivably, they might not be executed at all until the value of result is needed,
if ever. This is another potential application of lazy evaluation.
Java has steadily improved its support of concurrent programming over the years.
These approaches built upon the underlying Thread class and provided various
classes to support specific concurrent task such as pools.
The problem with these earlier approaches has been the need to learn these models
and decide if they are a good fit for the problem at hand. While this is necessary and
works well for many problem areas, it does require more effort on the part of the
developer to learn these techniques.
In Java 8, much of the effort requires to add concurrent behavior to a program has
been lessened allowing the developer to focus more on the problem at hand. This
support comes in the use of functions in conjunction with streams and collections.
[ 11 ]
For example, the next code sequence illustrates how a lambda expression can be
applied to each member of a stream. The Stream class's of method will generate a
stream of integers. The map function applies the lambda expression, x->x*2, to each
element of the stream:
Stream<Integer> stream = Stream.of(12, 52, 32, 74, 25);
stream.map(x->x*2)
.forEach(x ->System.out.println(x));
This can be parallelized easily using the parallel method as shown here:
stream = Stream.of(12, 52, 32, 74, 25);
stream.parallel().map(x->x*2)
.forEach(x ->System.out.println(x));
One possible output follows. However, since the stream operations are executed
in parallel, a different output ordering is possible:
64
148
50
104
24
[ 12 ]
Chapter 1
Later when we invoke the function, a value of the Optional<Customer> type will be
returned. We need to use the isPresent method to explicitly determine if a value is
returned. If it is present, then the get method returns the actual Customer instance
as shown next:
Optional<Customer>optionalCustomer = findCustomerWithID(123);
if (optionalCustomer.isPresent()) {
Customer customer = optionalCustomer.get();
// Use customer
} else {
// handle missing value
}
The problem with simply returning null is that the programmer may not realize that
a method may return null and may not attempt to handle it. This will result in a null
pointer exception. In this example, since the findCustomerWithID method explicitly
used the Optional type, we know and must deal with the possibility that nothing
may be returned.
[ 13 ]
The Optional type allows chained function calls where a method might not return
a value. We will demonstrate this in Chapter 6, Optional and Monads where the
Optional type is discussed in more detail.
The Optional type has a monadic structure. A monad is basically a structure
containing a set of computations represented as a series of steps. These computations
are chained together effectively forming a pipeline. However, there is more to
monads than this. Monads are a very useful technique and promote more reliable
programs than most imperative programming techniques are capable of doing.
You will learn more about the nature of monads and how to use them in Chapter 6,
Optional and Monads.
In the same way, as you need to choose the right hammer for a job, you also need
to choose the right language and programming style for the programming task. We
don't want to use a sledge hammer to put a small nail in the wall for a picture. Since
most jobs consist of multiple tasks, we need to use the right programming style for
the specific task at hand.
Hence, a major focus of the book is how to blend the various programming styles
available in Java 8 to meet an application's need. To be able to decide which
technique is best for a given job, one needs to understand the nature of the task
and how a technique supports such a task.
The incorporation of these functional programming techniques does not make Java a
functional programming language. It means that we now have a new set of tools that
we can use to solve the programming problems presented to us. It behooves us to
take advantage of these techniques whenever they are applicable.
Lambda expressions
Default methods
Functional interfaces
Collections
[ 14 ]
Chapter 1
Understanding these concepts will enable you to understand their purpose and why
they are used.
Lambda expressions
Lambda expressions are essentially anonymous functions. They can be considered to
be one of the most significant additions to Java 8. They can make the code easier to
write and read.
We have already seen a lambda expression in the previous examples. In this section,
we will provide additional detail about their form and use. There are three key
aspects to lambda expressions:
The following table illustrates several different forms a simple lambda expression
can take:
Lambda expression
Meaning
()->System.out.println()
x->System.out.println(x)
x->2*x
(x,y)->x+y
x -> {
int y = 2*x;
return y;
}
These examples are intended to provide some indication of what forms they may
take on. A lambda expression may have zero, one, or more parameters and may
return a value. They can be a concise single-line lambda expression or may consist
of multiple lines. However, they need to be used in some context to be useful.
You can use a lambda expression in most places where a block of code needs to
be executed. The advantage is that you do not need to formally declare and use
a method to perform some task.
[ 15 ]
Default methods
A default method is an interface method that possesses an implementation.
Traditionally, interfaces can only contain abstract methods or static and final
variables. This concept provides a way of defining a set of methods that a class can
implement, and by doing so, it provides an enhanced form of polymorphic behavior.
Adding a default method to an interface is simple. The method is added using
the default keyword along with its implementation. In the following example,
an interface called Computable is declared. It has one abstract method and two
default methods:
public interface Computable {
public int compute();
public default int doubleNumber(int num) {
return 2*num;
}
public default int negateNumber(int num) {
return -1*num;
}
}
To use a default method, we create a class that implements its interface and
executes the method against an instance of the new class. In the next sequence,
the ComputeImpl class is declared that implements the Computable interface:
public class ComputeImpl implements Computable {
@Override
public int compute() {
return 1;
}
}
[ 16 ]
Chapter 1
In the next code sequence, an array list is populated with three strings. The ArrayList
class implements the Iterable interface enabling the use of the forEach method:
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Peach");
list.add("Banana");
list.forEach(f->System.out.println(f));
The addition of a default method will not break code that was developed before
the method was added.
Functional interfaces
A functional interface is an interface that has one and only one abstract method. The
Computable interface declared in the previous section is a functional interface. It has
one and only one abstract method: compute. If a second abstract method was added,
the interface would no longer be a functional interface.
Functional interfaces facilitate the use of lambda expressions. This is illustrated
with the Iterable interface's forEach method. It expects a lambda expression
that implements the Consumer interface. This interface has a single abstract
method, accept, making it a functional interface.
[ 17 ]
This means that the forEach method will accept any lambda expression that
matches the accept method's signature as defined here:
void accept(T t)
That is, it will use any lambda expression that is passed a single value and returns
void. As seen with the ArrayList class used in the previous section and duplicated
next, the lambda expression matches the signature of the accept method.
list.forEach(f->System.out.println(f));
This is possible because Java 8 uses a technique called type inference to determine
if the lambda expression can be used.
Java 8 has introduced a number of functional interfaces. However, conceptually they
have been present in earlier version of Java, but were not identified as functional
interfaces. For example, the Runnable interface, with its single abstract run method,
is a functional interface. It has been a part of Java since the very beginning, but until
Java 8 was not labeled as a functional interface.
The advantage of functional interfaces is that Java is able to automatically use a
lambda expression that matches the signature of the abstract method found in a
functional interface. Consider the creation of a thread as illustrated in the following
code sequence:
new Thread(()-> {
for(inti=0; i<5; i++) {
System.out.println("Thread!");
}
}).start();
[ 18 ]
Chapter 1
.map(x -> x * 2)
.forEach(x ->System.out.println(x));
We can duplicate this sequence using a method reference in place of the lambda
expression as shown next. A method reference takes the form of a class name
followed by a double colon and then the method name. The parameter is implied,
and the code will produce the same output as the previous example.
Stream<Integer> stream = Stream.of(12, 52, 32, 74, 25);
Stream
.map(x -> x * 2)
.forEach(System.out::println);
In the following example, two method references are used where the first one
invokes the sin method against each element of the list:
stream
.map(Math::sin)
.forEach(System.out::println);
We can also use constructors in a similar manner. Method and constructor references
provide a convenient and easy way of using methods and constructors where
lambda expressions are used.
[ 19 ]
Collections
The Collection interface has been enhanced in Java with the addition of methods
that return a Stream object based on the collection. The stream method returns a
stream executed in sequential order while the parallelStream method returns a
stream that is executed concurrently. The following example illustrates the use of the
stream method as applied against the list object. The List interface extends the
Collection interface:
String names[] = {"Sally", "George", "Paul"};
List<String> list = Arrays.asList(names);
Stream<String> stream = list.stream();
stream.forEach(name ->System.out.println(name + " - "
+ name.length()));
In addition, since the Collection interface inherits the Iterable interface's forEach
method from the iterator. We can use this with the previous List object:
list.forEach(name ->System.out.println(name + " - "
+ name.length()));
There are other enhancements to collections in Java 8, which we will present as they
are encountered.
Summary
In this chapter, we introduced many of the features that constitute a functional
programming language. These included functions and the idea that they can be
combined in more powerful ways than are possible in an imperative type language.
Functional languages frequently allow the expression of program logic using a
fluent style where function invocations build upon each other. The expression of
parallel behavior is simplified in functional programming languages allowing better
optimization of code.
An important goal of functional programs has been to minimize the use of mutable
data and avoid side effects. This also promotes certain optimizations and makes
functional code more maintainable. Recursion is central to functional programming
languages, and we hinted at how it can be used. The use of optional types and
monads were also introduced.
[ 20 ]
Chapter 1
Java 8 introduced several new language features that support the use of functions.
These include lambda expressions, which underlie functions and functional
interfaces with type inferences. The introduction of default methods enables the
newer functional techniques to be used with older interfaces and classes. Method
and constructor references provide a way of using these constructs where lambda
expressions are expected.
With many of these topics, we provided simple examples of how Java can support
these concepts. The remainder of the book provides a much more detailed discussion
of how Java can be used.
Java is not a pure functional programming language. However, it supports many
functional style techniques, which a knowledgeable developer can use. The use
of these techniques require a different way of thinking about and approaching
problems. We will convey these techniques in this book starting with a more detailed
discussion of functions in Chapter 2, Putting the Function in Functional Programming.
[ 21 ]
www.PacktPub.com
Stay Connected: