Home Programming Learning Java Functional Programming

Learning Java Functional Programming

By Richard M Reese , Richard M. Reese
books-svg-icon Book
eBook $47.99 $32.99
Print $60.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $47.99 $32.99
Print $60.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Getting Started with Functional Programming
About this book
Functional programming is an increasingly popular technology that allows you to simplify many tasks that are often cumbersome and awkward using an object-oriented approach. It is important to understand this approach and know how and when to apply it. Functional programming requires a different mindset, but once mastered it can be very rewarding. This book simplifies the learning process as a problem is described followed by its implementation using an object-oriented approach and then a solution is provided using appropriate functional programming techniques. Writing succinct and maintainable code is facilitated by many functional programming techniques including lambda expressions and streams. In this book, you will see numerous examples of how these techniques can be applied starting with an introduction to lambda expressions. Next, you will see how they can replace older approaches and be combined to achieve surprisingly elegant solutions to problems. This is followed by the investigation of related concepts such as the Optional class and monads, which offer an additional approach to handle problems. Design patterns have been instrumental in solving common problems. You will learn how these are enhanced with functional techniques. To transition from an object-oriented approach to a functional one, it is useful to have IDE support. IDE tools to refactor, debug, and test functional programs are demonstrated through the chapters. The end of the book brings together many of these functional programming techniques to create a more comprehensive application. You will find this book a very useful resource to learn and apply functional programming techniques in Java.
Publication date:
October 2015
Publisher
Packt
Pages
296
ISBN
9781783558483

 

Chapter 1. Getting Started with Functional Programming

Functional programming languages have been used successfully for decades and present a different and often more elegant way of expressing the program logic. Functional languages, such as Lisp, Clojure, or Haskell, incorporate techniques that may seem odd and hard to follow by programmers who are used to imperative programming techniques.

A language such as Java, while not initially developed with a functional orientation, can incorporate functional techniques. This is the major change to the language made with the release of Java 8. Java now incorporates imperative, procedural, object-oriented, and functional techniques.

It is possible to write a non-object-oriented program in Java. Likewise, it is possible to write a nonfunctional program in Java 8. The goal of this book is to enlighten the reader to the nature of functional programming techniques and how to incorporate these techniques in Java 8 applications.

We will start with a discussion of the attributes commonly associated with functional programming. From there, we will examine the support, Java provides for developing applications using a functional-style programming approach.

A predominant feature of functional programming languages is the use of functions. The term, function, is generally understood to be:

  • A way of expressing an algorithm

  • A mathematical function

  • Where the goal is to avoid state changes and mutable data

In functional programming, applications are constructed using only pure functions. A pure function is a function which does not have side effects. A side effect occurs when a function does something else besides simply returning a value, such as mutating a global variable or performing IO. In this chapter, we will examine the major aspects of functional programming including:

  • Functions and function composition

  • Fluent interfaces

  • Strict and non-strict evaluation

  • Persistent data structures, monads, and the Optional class

  • Recursion and parallelism

This is followed by the support Java 8 provides for functional programming, including:

  • Lambda expressions

  • Default methods

  • Functional interface

  • Method and constructor references

  • Collections

In addition, to our discussion of functional programming support as provided by Java 8, refactoring, debugging, and testing Java 8 code are also important topics, which need to be addressed. These topics are covered in Chapter 8, Refactoring, Debugging, and Testing.

So, let's begin with an overview of what constitutes the functional programming approach.

 

Aspects of functional programming


Functions can be simple or complex, but simpler functions are preferred. The function should ideally not change the state of memory or perform I/O, and consequently work with immutable data. These later two concepts are explored in Chapter 6, Optional and Monads.

There are several aspects of functional programming languages that we will explore here. They include:

  • Functions

  • Function composition

  • Fluent interfaces

  • Strict versus non-strict evaluation

  • Parallelism

  • Persistent data structures

  • Recursion

  • Optional and monads

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:

  • Take a function as an argument

  • 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)

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));

Tip

Downloading the example code

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

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).

  • With no dependencies between multiple pure functions, they can be reordered and performed in parallel. They are essentially thread safe.

  • 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:

We can evaluate it for the value of 3 and 4 as follows, returning a result of 7:

If we substitute 3 for x we get:

Next, if we define g(y) as:

Then, the following is also true:

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.

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));

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));

The output of both display statements will be 25.

We could have obtained the same results with a series of imperative statements. However, this does not result in as much flexibility as can be obtained using function composition. The ability to pass functions will provide the enhanced flexibility. We will postpone a detailed discussion of this approach until Chapter 3, Function Composition and Fluent Interfaces.

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);

This will generate the following output:

2015-03-22
2017-02-25

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.

Strict versus non-strict evaluation

Functional languages can be classified as either using strict or non-strict evaluation of expressions. With strict evaluation, sometimes called eager evaluation, the expressions are evaluated as they are encountered.

With non-strict evaluation, they are not evaluated until necessary. Non-strict evaluation is sometimes called lazy evaluation. However, these terms are not always strict synonyms. Non-strict evaluation is concerned with the semantics of the expression, while lazy evaluation deals more with how the expression is evaluated.

Lazy evaluation is supported using streams. A stream can be thought of as a series of elements that flow like a river or stream. They add a convenient means of processing data in an easy-to-use and natural manner. The stream concept is support in Java 8 with the Stream class.

In the following sequence, a stream is created by generating five random numbers, sorting these numbers, and then displaying them:

Random random = new Random();
random.ints()
    .limit(5) 
    .sorted()
    .forEach(x->System.out.println(x));

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.

Persistent data structures

Persistent data structures maintain previous versions of itself. As changes to the data structure occur, a new version of the data structure is created while maintaining the older version. These structures are effectively immutable.

Avoiding the mutation of data obviously has no side effects. This means that they are thread-safe and enable various optimization techniques.

One consequence of mutable data is that, when accessed from multiple threads, many threading issues arise that can make a program less reliable and maintainable. If the data is immutable, then these threading issues such as the need to synchronize data access largely go away.

One approach used by functional programming languages to simulate state is using a data structure, which is passed to a function. The data structure is copied and any changes made are reflected in the new copy of the data structure. This is referred to a state-passing style and can use a considerable amount of memory unless appropriate optimization techniques are applied.

There are immutable collections that support the concept of persistent data structures. However, when a language is entirely immutable, then a large amount of garbage is generated requiring extensive optimization to be useful. Some of these collections are not practical when they contain a significant number of elements.

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:

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));

It will generate the same output: 120.

Indirect recursion occurs when a function calls itself but not immediately. For example, if function A calls function B, which then calls function A, we have indirect recursion.

A recursion function needs to be bounded. This means that it must stop calling itself at some time. Otherwise, it will exceed the system's resources and result in an exception being thrown and the program terminating abnormally. In the factorial function, the test for an n value of 1 stopped the recursion.

Frequently, recursion is implemented using a program stack. However, if tail recursion is used, then the compiler can avoid the use of a program stack and use essentially the same technique used to implement an imperative loop. Tail recursion involves a tail call, which is where the recursive call is the last statement of the function.

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.

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));

The output follows:

24
104
64
148
50

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

When the lambda expression is executed concurrently on different elements of the stream, the operations can be assigned to different processors and at different times. There is no guarantee with regard to the order in which the operations will be executed.

Humans are not very adept at multitasking, let alone writing concurrent programs that are reliable. By moving some of the decision-making process to the compiler and runtime system, more capable and efficient programs can be created.

Optional and monads

Null pointer exceptions are common, and their very existence is problematic to many developers. They introduce a slew of problems, including the need to handle them gracefully. The Optional class has been introduced in Java 8 to help deal with null pointer exceptions. It helps preserve type safety. The approach will ease the use of functions, provide an opportunity for using fluent interfaces, and avoid exception handling boilerplate code.

The intent of the Optional class is to help programmers deal with situations where a failure may occur. One way of handling this type of problem has been to return a null reference indicating a missing value. Using the Optional class forces the programmer to explicitly deal with the possibility that a function might not return a value. The Optional type should be used as the return type of a method or function that might not return a value.

Consider the situation where we would like to return an instance of a Customer class based on an ID using the following method:

    public Optional<Customer>findCustomerWithID(long id) {
        //...
        return someValue;
    }

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.

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.

 

Java 8's support for functional style programming


So, what is the foundation for functional style programming in Java 8? Well, it comes from a number of additions and modifications to the language. In this section, we will briefly introduce several concepts that Java 8 uses. These include:

  • Lambda expressions

  • Default methods

  • Functional interfaces

  • Method and constructor references

  • Collections

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:

  • They are a block of code

  • They may be passed parameters

  • They may return a value

The following table illustrates several different forms a simple lambda expression can take:

Lambda expression

Meaning

()->System.out.println()

It takes no arguments and displays a single line

x->System.out.println(x)

It takes a single argument and displays it on a line

x->2*x

It takes a single argument and returns its double

(x,y)->x+y

It takes two arguments and returns their sum

x -> {
int y = 2*x;
    return y;
}

It takes a single argument and returns its double using multiple statements

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.

Lambda expressions are often converted to a functional interface automatically simplifying many tasks. Lambda expressions can access other variables outside of the expression. The ability to access these types of variables is an improvement over anonymous inner functions, which have problems in this regard. Lambda expressions will be discussed in more detail in Chapter 2, Putting the Function in Functional Programming.

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;
        } 
    }

Next, an instance of ComputeImpl is declared, and the default method is executed:

ComputeImplcomputeImpl  = new ComputeImpl();
System.out.println(computeImpl.doubleNumber(2));

The result will be a 4. We did not have to provide an implementation of the doubleNumber method in the ComputeImpl class before we used it. However, we can override if desired.

In Java 8, we can add default and static methods to interfaces. This has a number of advantages, including the ability to add capability to previous interfaces without breaking the existing code. This has allowed interfaces declared prior to Java 8 to be augmented with a default method that supports functional-type operations.

For example, the forEach method has been added as a default method to the java.lang package's Iterable interface. This method takes a lambda expression that matches the Consumer interface's accept method and executes it against each member of the underlying collection.

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.

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();

The argument of the Thread class's constructor is a lambda expression that implements the Runnable interface's run method. This method takes zero argument and returns void. The lambda expression used matches this signature.

Method and constructor references

A method or constructor reference is a technique that allows a Java 8 programmer to use a method or constructor as if it was a lambda expression. In the following sequence, a stream is generated, its elements are doubled, and then displayed using a lambda expression:

    Stream<Integer> stream = Stream.of(12, 52, 32, 74, 25);
    Stream
        .map(x -> x * 2)
        .forEach(x ->System.out.println(x));

The output follows:

24
104
64
148
50

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);

The output follows:

-0.5365729180004349
0.9866275920404853
0.5514266812416906
-0.9851462604682474
-0.13235175009777303

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.

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()));

This sequence output follows:

Sally - 5
George - 6
Paul - 4

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.

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.

About the Authors
  • Richard M Reese

    Richard M. Reese has worked in both industry and academia. For 17 years, he worked in the telephone and aerospace industries, serving in several capacities, including research and development, software development, supervision, and training. He currently teaches at Tarleton State University, where he has the opportunity to apply his years of industry experience to enhance his teaching. 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, natural language processing, functional programming, networks, and data science.

    Browse publications by this author
  • Richard M. Reese

    Richard Reese has worked in the industry and academics for the past 29 years. For 10 years he provided software development support at Lockheed and at one point developed a C based network application. He was a contract instructor providing software training to industry for 5 years. Richard is currently an Associate Professor at Tarleton State University in Stephenville Texas. Richard is the author of various books and video courses some of which are as follows: Natural Language Processing with Java. Java for Data Science Getting Started with Natural Language Processing in Java

    Browse publications by this author
Latest Reviews (11 reviews total)
no support at all can not download code examples can not login to your site.
pragmatic and effective but it's not for purist; can be the right springboard to new knowledge
Learning Java Functional Programming
Unlock this book and the full library FREE for 7 days
Start now