Java programming with lambda expressions

A mathematical example demonstrates the power of lambda expressions in Java

Java  >  Lambda expressions  >  Lambda symbol / abstract formulas / binary code
Monsitj / Getty Images

In the technical keynote address for JavaOne 2013, Mark Reinhold, chief architect for the Java Platform Group at Oracle, described lambda expressions as the single largest upgrade to the Java programming model ever. While there are many applications for lambda expressions, this article focuses on a specific example that occurs frequently in mathematical applications; namely, the need to pass a function to an algorithm.

As a gray-haired geek, I have programmed in numerous languages over the years, and I have programmed extensively in Java since version 1.1. When I started working with computers, almost no one had a degree in computer science. Computer professionals came mostly from other disciplines such as electrical engineering, physics, business, and mathematics. In my own former life I was a mathematician, and so it should come as no surprise that my initial view of a computer was that of a giant programmable calculator. I've broadened my view of computers considerably over the years, but I still welcome the opportunity to work on applications that involve some aspect of mathematics.

Many applications in mathematics require that a function be passed as a parameter to an algorithm. Examples from college algebra and basic calculus include solving an equation or computing the integral of a function. For over 15 years Java has been my programming language of choice for most applications, but it was the first language that I used on a frequent basis that did not allow me to pass a function (technically a pointer or reference to a function) as a parameter in a simple, straightforward manner. That shortcoming is about to change with the upcoming release of Java 8.

The power of lambda expressions extends well beyond a single use case, but studying various implementations of the same example should leave you with a solid sense of how lambdas will benefit your Java programs. In this article I will use a common example to help describe the problem, then provide solutions written in C++, Java before lambda expressions, and Java with lambda expressions. Note that a strong background in mathematics is not required to understand and appreciate the major points of this article.

Lambda expressions in a mathematical example

The example used throughout this article is Simpson's Rule from basic calculus. Simpson's Rule, or more specifically Composite Simpson's Rule, is a numerical integration technique to approximate a definite integral. Don't worry if you are unfamiliar with the concept of a definite integral; what you really need to understand is that Simpson's Rule is an algorithm that computes a real number based on four parameters:

  • A function that we want to integrate.
  • Two real numbers a and b that represent the endpoints of an interval [a,b] on the real number line. (Note that the function referred to above should be continuous on this interval.)
  • An even integer n that specifies a number of subintervals. In implementing Simpson's Rule we divide the interval [a,b] into n subintervals.

To simplify the presentation, let's focus on the programming interface and not on the implementation details. (Truthfully, I hope that this approach will let us bypass arguments about the best or most efficient way to implement Simpson's Rule, which is not the focus of this article.) We will use type double for parameters a and b, and we will use type int for parameter n. The function to be integrated will take a single parameter of type double and a return a value of type double.

Function parameters in C++

To provide a basis for comparison, let's start with a C++ specification. When passing a function as a parameter in C++, I usually prefer to specify the signature of the function parameter using a typedef. Listing 1 shows a C++ header file named simpson.h that specifies both the typedef for the function parameter and the programming interface for a C++ function named integrate. The function body for integrate is contained in a named simpson.cpp (not shown) and provides the implementation for Simpson's Rule.

Listing 1. C++ header file for Simpson's Rule

#if !defined(SIMPSON_H)
#define SIMPSON_H
#include <stdexcept>
using namespace std;
typedef double DoubleFunction(double x);
double integrate(DoubleFunction f, double a, double b, int n)

Calling integrate is straightforward in C++. As a simple example, suppose that you wanted to use Simpson's Rule to approximate the integral of the sine function from 0 to π (PI) using 30 subintervals. (Anyone who has completed Calculus I should be able to compute the answer exactly without the help of a calculator, making this a good test case for the integrate function.) Assuming that you had included the proper header files such as <cmath> and "simpson.h", you would be able to call function integrate as shown in Listing 2.

Listing 2. C++ call to function integrate

double result = integrate(sin, 0, M_PI, 30);

That's all there is to it. In C++ you pass the sine function as easily as you pass the other three parameters.

Java without lambda expressions

Now let's look at how Simpson's Rule might be specified in Java. Regardless of whether or not we are using lambda expressions, we use the Java interface shown in Listing 3 in place of the C++ typedef to specify the signature of the function parameter.

Listing 3. Java interface for the function parameter

public interface DoubleFunction
    public double f(double x);

To implement Simpson's Rule in Java we create a class named Simpson that contains a method, integrate, with four parameters similar to what we did in C++. As with a lot of self-contained mathematical methods (see, for example, java.lang.Math), we will make integrate a static method. Method integrate is specified as follows:

Listing 4. Java signature for method integrate in class Simpson

public static double integrate(DoubleFunction df, double a, double b, int n)

Everything that we've done thus far in Java is independent of whether or not we will use lambda expressions. The primary difference with lambda expressions is in how we pass parameters (more specifically, how we pass the function parameter) in a call to method integrate. First I'll illustrate how this would be done in versions of Java prior to version 8; i.e., without lambda expressions. As with the C++ example, assume that we want to approximate the integral of the sine function from 0 to π (PI) using 30 subintervals.

Using the Adapter pattern for the sine function

In Java we have an implementation of the sine function available in java.lang.Math, but with versions of Java prior to Java 8, there is no simple, direct way to pass this sine function to the method integrate in class Simpson. One approach is to use the Adapter pattern. In this case we would write a simple adapter class that implements the DoubleFunction interface and adapts it to call the sine function, as shown in Listing 5.

Listing 5. Adapter class for method Math.sin

import com.softmoore.math.DoubleFunction;
public class DoubleFunctionSineAdapter implements DoubleFunction
    public double f(double x)
        return Math.sin(x);

Using this adapter class we can now call the integrate method of class Simpson as shown in Listing 6.

Listing 6. Using the adapter class to call method Simpson.integrate

DoubleFunctionSineAdapter sine = new DoubleFunctionSineAdapter();
double result = Simpson.integrate(sine, 0, Math.PI, 30);

Let's stop a moment and compare what was required to make the call to integrate in C++ versus what was required in earlier versions of Java. With C++, we simply called integrate, passing in the four parameters. With Java, we had to create a new adapter class and then instantiate this class in order to make the call. If we wanted to integrate several functions, we would need to write an adapter class for each of them.

We could shorten the code needed to call integrate slightly from two Java statements to one by creating the new instance of the adapter class within the call to integrate. Using an anonymous class rather than creating a separate adapter class would be another way to slightly reduce the overall effort, as shown in Listing 7.

Listing 7. Using an anonymous class to call method Simpson.integrate

DoubleFunction sineAdapter = new DoubleFunction()
    public double f(double x)
        return Math.sin(x);
double result = Simpson.integrate(sineAdapter, 0, Math.PI, 30);

Without lambda expressions, what you see in Listing 7 is about the least amount of code that you could write in Java to call the integrate method, but it is still much more cumbersome than what was required for C++. I am also not that happy with using anonymous classes, although I have used them a lot in the past. I dislike the syntax and have always considered it to be a clumsy but necessary hack in the Java language.

Java with lambda expressions and functional interfaces

Now let's look at how we could use lambda expressions in Java 8 to simplify the call to integrate in Java. Because the interface DoubleFunction requires the implementation of only a single method it is a candidate for lambda expressions. If we know in advance that we are going to use lambda expressions, we can annotate the interface with @FunctionalInterface, a new annotation for Java 8 that says we have a functional interface. Note that this annotation is not required, but it gives us an extra check that everything is consistent, similar to the @Override annotation in earlier versions of Java.

The syntax of a lambda expression is an argument list enclosed in parentheses, an arrow token (->), and a function body. The body can be either a statement block (enclosed in braces) or a single expression. Listing 8 shows a lambda expression that implements the interface DoubleFunction and is then passed to method integrate.

Listing 8. Using a lambda expression to call method Simpson.integrate

DoubleFunction sine = (double x) -> Math.sin(x);
double result = Simpson.integrate(sine, 0, Math.PI, 30);

Note that we did not have to write the adapter class or create an instance of an anonymous class. Also note that we could have written the above in a single statement by substituting the lambda expression itself, (double x) -> Math.sin(x), for the parameter sine in the second statement above, eliminating the first statement. Now we are getting much closer to the simple syntax that we had in C++. But wait! There's more!

The name of the functional interface is not part of the lambda expression but can be inferred based on the context. The type double for the parameter of the lambda expression can also be inferred from the context. Finally, if there is only one parameter in the lambda expression, then we can omit the parentheses. Thus we can abbreviate the code to call method integrate to a single line of code, as shown in Listing 9.

Listing 9. An alternate format for lambda expression in call to Simpson.integrate

double result = Simpson.integrate(x -> Math.sin(x), 0, Math.PI, 30);

But wait! There's even more!

Method references in Java 8

Another related feature in Java 8 is something called a method reference, which allows us to refer to an existing method by name. Method references can be used in place of lambda expressions as long as they satisfy the requirements of the functional interface. As described in the resources, there are several different kinds of method references, each with a slightly different syntax. For static methods the syntax is Classname::methodName. Therefore, using a method reference, we can call the integrate method in Java as simply as we could in C++. Compare Java 8 call shown in Listing 10 below with original C++ call shown in Listing 2 above.

Listing 10. Using a method reference to call Simpson.integrate

double result = Simpson.integrate(Math::sin, 0, Math.PI, 30);
1 2 Page 1
Page 1 of 2
How to choose a low-code development platform