P8tech logo      

Java 8 Lambda Expressions

The addition of lambda expressions to Java 8 is a step toward - encouraging the use of function programming techniques in Java applications. A functional style programming orientation places emphasis on using simple functions in various contexts. The use of function programming can result in better and less error prone code in many situations. This style of programming has been integrated with the object oriented nature of Java. It is also reflected in how core libraries are organized.

Lambda constructs are found in a number of other programming languages. It was felt that Java could also benefit by adding a functional programming aspect to the language which enables the use of concise and elegant expressions. Lambda expressions also allow a function to be used as a method argument. This treats code as data.

Lambda expressions eliminate the need to use anonymous inner classes in many situations. There is a lot of boiler plate code used with anonymous inner classes. After a while they all seem to look a lot alike, and, typically, they have a single method that is being overridden. Using lambda expressions means that more compact code can be used. In addition, their use also avoids the confusion that can occur when using the this keyword and the problems of accessing non-final variables.

The use of lambda expressions helps distinguish between what should be done versus how it is done. The what is expressed in the expression proper. The how is determined in the implementation. This is typified by the use of lambda expressions with streams, which can be used to implement the functionality in a sequential or concurrent manner.  Streams are discussed in Chapter 3.

Multiple core systems are supported by allowing elements of a collection to be processed by functions without the developer needing to worry about how to explicitly implement concurrent behavior. The Stream interface along with the use of the forEach method, in conjunction with lambda expressions, can take advantage of multiple cores.

When lambda expressions are compiled, they use the invokedynamic JVM instruction added to Java 7. As a result, the compiler is able to generate shorter byte code sequences. This translates to better performance than equivalent code which did not use lambda expressions.

In this chapter, we will show how lambda expressions can be created and used in a variety of contexts. Lambda expression syntax variations are fully explored so that one can fully understand how they can be written. The concept of closure and its relationship to lambda expressions will be discussed. We will also see how method and constructor references can be used as lambda expressions.

The last section of the chapter explores the java.util.function package. This package contains a number of useful functional interfaces that are used with lambda expressions. Understanding these interfaces is especially important when they are used with the Stream interface.

Note: The mathematician Alonzo Church developed the concept of lambda expressions as part of a model of computation. He used the Greek letter lambda to represent this abstraction. The name has stuck.

Understanding Lambda Expressions

A lambda expression can be thought of as an anonymous function. When we think of a function, a common image of a square root function might come to mind. We pass it a number and it returns a number.

A guiding force behind lambda expressions is to make these expressions as simple to use as possible. What is the simplest form of a function? Well, one version of a simple square function follows:

int square(int num) {

                                return num*num;

}

 

If we ignore the data types and leave off the name, we can conceivably write it as:

num {return num*num;}

 

Since we know the function will return a value, there is no need for the return keyword, at least with this function. Also there is no need for a block statement since there is only one statement being used. We still need to separate the parameter from the function’s body. This can be done using the lambda operator (->), sometimes called the arrow token, as shown below:

num -> num*num

 

This is our lambda expression. It represents a simple way of writing a function. Here is another simple lambda expression. This one is passed a single value to be displayed and nothing is returned.

x -> System.out.println(x)

 

Where do we use a lambda expression? You can use a lambda expression anywhere a functional interface is expected. A functional interface is an interface that has one and only one abstract method. This method is called the functional method. These concepts were discussed in Chapter 1.

To illustrate, we will use a lambda expression as the argument of the forEach method. This method expects an instance of the java.util.function.Consumer functional interface. Its abstract method is the accept method which accepts an object and returns void.

The forEach method has been added to a number of interfaces in Java 8 including the Iterable interface.  Any sub-interfaces or implementing classes of the Iterable interface will possess this method. This includes quite a few interfaces and classes including: List, HashSet, and Linkedlist.

In the following example, a List is created based on a series of numbers:

List<Integer> list = Arrays.asList(5,3,4);

 

The lambda expression is used below to display each element of the list:

 list.forEach((Integer element)->System.out.println(element));

 

The output of this code sequence follows:

5

3

4

What is happening here? When the compiler examines the statement it expects an instance of an Iterable. However, what it finds is the lambda expression. It then tries to convert the lambda expression into the needed object. It decides it can do this since only a single argument is passed and nothing is returned. It matches the signature of the accept method. Since list is a set of integers, it decides the parameter type should be Integer. This process is called target typing and is discussed in depth in the next section.

You can envision the compiler generating an anonymous inner class implementation for the previous example similar to the following:

list.forEach(new Consumer() {

                                @Override

                                public void accept(Object element) {

                                                System.out.println(element);

                                }

});

 

However, a more concise and ultimately more efficient approach uses a lambda expression as follows:

list.forEach((element)->System.out.println(element));

 

This implies an object is created. Whether or not this actually happens is an implementation issue. It is more useful to think of lambda expressions as anonymous functions.

While functions and methods are similar, a function is not tied to a class.  Lambda expressions are conceptually a simple concept. However, their syntax can be cryptic until fully understood. They represent a powerful new tool to develop Java applications.

Inferring the Type of the Target

Target typing is the process of inferring the type of an object based on the context in which it is being used. We saw this occur in the previous example. Type referencing occurs when the system determines the type of the lambda expression. The type is inferred from the expression. This is possible because Java knows the expected parameter type for the functional interface’s abstract method.

A lambda expression does not contain information about which functional interface it is implementing. The context of the lambda expression determines which functional interface it represents. For example, the following lambda expression accepts a single value and returns its square:

x -> x * x

 

The following functional interface definitions both have a single abstract method that accepts a single value and returns a value:

interface IntegerSquare {

                                                int square(int x);

}

 

interface DoubleSquare {

                                double square(double x);

}

 

However, either of the next two assignments will work. We are assigning a lambda expression to a functional interface variable, which matches both of the abstract method’s signature.

IntegerSquare intSquare = x -> x * x;

DoubleSquare doubleSquare = x -> x * x;       

 

Here is a complete example:

public class LambdaExpressionExamples {

 

                                public static void main(String[] args) {

                                                IntegerSquare intSquare = x -> x * x;

                                                DoubleSquare doubleSquare = x -> x * x;

                                                System.out.println(intSquare.square(5));

                                                System.out.println(doubleSquare.square(5));

                                }   

}

 

The output of this sequence follows:

25

25.0   

 

Let’s reexamine the following statement:

IntegerSquare intSquare = x -> x * x;

 

The left-hand side of this expression is an IntegerSquare and is referred to as the lambda expression’s target type. The target type is the data type expected in a given context. The target type for a lambda expression must be:

- A functional interface

- Have compatible parameter types as the interface’s function descriptor

- Have a return type compatible with the return type of the interface’s function descriptor

- Throw only those exceptions permitted by the function descriptor

As a reminder, a function descriptor is the method type of the functional method. It consists of the parameter types, return type, and any exceptions thrown by the method.

We can also use generic definitions of functional interfaces. To illustrate this we will use the ComputableType interface as shown here:

interface ComputableType<T> {

                    T compute(T x);

}

 

In the next example, we declare two variables based on this interface. The first is of type Integer while the second is of type Double. The examples illustrate assigning lambda expressions to each variable using both implicit and explicit parameter declarations.

ComputableType<Integer> computableInteger;

computableInteger = (Integer x) -> x * x;

computableInteger = (x) -> x * x;

 

ComputableType<Double> computableDouble;

computableDouble = (Double x) -> x * x;

computableDouble = (x) -> x * x;

 

The last statement in each group is identical except for the variable the expression is being assigned to. In these cases the compiler is able to determine the type to use. The expression is being assigned to two different targets. This is another example of target typing.

While lambda expressions can be considered objects, they do not necessarily assign a unique identifier. This is a design decision which will provide Java architects with flexibility in how lambda expressions may evolve in future versions of Java. As a result, the use of the equality operator and the equals method (inherited from Object) will not always perform as expected. Consider the following declaration:

ComputableType<Integer> ci1;

 

Both of the following comparisons will generate syntax errors:

if(ci1 == (x) -> x * x) { }

if(ci1 == ((x) -> x * x)) { }

 

The message generated follows:

lambda expression not expected here

bad operand types for binary operator '=='

  first type:  ComputableType<Integer>

  second type: <none>

We can eliminate this syntax error by casting the lambda expression using ComputableType<Integer> as shown below. In this example, we also perform comparisons between two ComputableType variables to determine how they behave.

ComputableType<Integer> ci1;

ComputableType<Integer> ci2;

       

ci1 = (x) -> x * x;

ci2 = (x) -> x * x;

System.out.println(ci1 ==

        (ComputableType<Integer>)(x) -> x * x);

System.out.println(ci1.equals(

        (ComputableType<Integer>)(x) -> x * x));

System.out.println(ci1 == ci2);

System.out.println(ci1.equals(ci2));

ci2 = ci1;

System.out.println(ci1 == ci2);

System.out.println(ci1.equals(ci2));

 

The output follows:

false

false

false

false

true

true

 

Comparison of lambda expressions will not always work as one might expect.


Creating Lambda Expressions

In this section, we will see how lambda expressions are created and examine lambda expression syntax issues. The basic syntax of a lambda expression follows:

(parameter) -> expression

(parameters) -> { statements;}

 

A lambda expression consists of the following element sequence:

- Parameter list

- The lambda operator (->)

- A body which is either a single statement or a statement block

The parameter list is typically a comma-delineated list of formal parameters enclosed in parentheses. The parameter list does not necessarily require:

- A data type, or

- A set of parentheses

The parameter type may be declared explicitly or may be inferred from its context. Lambda expression can take on several forms.

Table 1: Lambda Expression Forms illustrate various combinations of input and return values.

Table 1: Lambda Expression Forms

Number of Parameters | Number of Return Values | Example

0 | 0 | () -> System.out.println("")

0 | 1 | () -> 45

1 | 0 | (x) -> System.out.println(x)

1 | 1 | (x) -> x * x

Multiple | 0 | (x, y) -> System.out.println(x + y)

Multiple | 1 | (x, y) -> x + y

 

Table Lambda Expressions | Java 8


Content from Java 8 New Features

Java 8 Lambda Expressions