Lambda Expressions - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 6. Lambda Expressions

There are very few means by which a new feature in an existing language can have a significant impact on the ecosystem. Lambda expressions for the Java language are one such significant new feature that will have an effect on many facets of the ecosystem. Simply defined, lambda expressions are a convenient way to create anonymous functions. They provide an easy way to create a single method interface using an expression or series of statements. Lambda expressions are built upon functional interfaces, which are interfaces that contain a single abstract method. They can be applied in many different contexts, ranging from simple anonymous functions to sorting and filtering Collections. Moreover, lambda expressions can be assigned to variables and then passed into other objects.

In this chapter, you will learn how to create lambda expressions, and you’ll see many examples of how they can be applied in common scenarios. You’ll also learn how to generate the building blocks for lambda expressions, so that you can construct applications to facilitate them. The chapter will delve into the java.util.function package, which contains a bevy of useful functional interfaces that lambdas can implement. Lastly, you will see how to simplify certain types of lambda expressions into method references for a more concise approach.

After reading this chapter, you to will be able to see the impact that lambda expressions have on the Java language. They modernize the language by making developers more productive, and opening new possibilities in many areas. Lambda expressions turn the page on Java, bringing the language into a new light, with the likes of other languages that have had similar constructs for some time. Those languages helped to pave the way for lambda expressions in the Java language, and there is no doubt that lambda expressions will pave the way for many elegant solutions.

6-1. Writing a Simple Lambda Expression

Problem

You want to encapsulate a piece of functionality that prints out a simple message.

Solution

Write a lambda expression that accepts a single parameter that contains the message you want to print, and implement the printing functionality within the lambda. In the following example, a functional interface, HelloType, is implemented via a lambda expression and assigned to the variable helloLambda. Lastly, the lambda is invoked, printing the message.

public class HelloLambda {

/**
* Functional Interface
*/
public interface HelloType {
/**
* Function that will be implemented within the lambda
* @param text
*/
void hello(String text);
}

public static void main(String[] args){
// Create the lambda, passing a parameter named "text" to the
// hello() method, returning the string. The lambda is assigned
// to the helloLambda variable.
HelloType helloLambda =
(String text) -> {System.out.println("Hello " + text);};

// Invoke the method call
helloLambda.hello("Lambda");
}
}

Results:

Hello Lambda

How It Works

A lambda expression is an anonymous block of code that encapsulates an expression or a series of statements and returns a result. Lambda expressions are also known as closures in some other languages. They can accept zero or more parameters, any of which can be passed with or without type specification since the type can be automatically derived from the context. While it is possible for code written in the Java language to move forward without the use of lambda expressions, they are an important addition that greatly improves overall maintainability, readability, and developer productivity. Lambda expressions are an evolutionary change to the Java language, as they are another step toward modernization of the language, and help keep it in sync with other languages.

The syntax of a lambda expression includes an argument list, a new character to the language known as the “arrow token” (->), and a body. The following model represents the structure of a lambda expression:

(argument list) -> { body }

The argument list for a lambda expression can include zero or more arguments. If there are no arguments, then an empty set of parentheses can be used. If there is only one argument, then no parentheses are required. Each argument on the list can include an optional type specification. If the type of the argument is left off, then the type is derived from the current context.

In the solution for this recipe, curly braces surround the body of a block, which contains more than a single expression. The curly braces are not necessary if the body consists of a single expression. The curly braces in the solution could have been left off, but they’ve been included for ease of readability. The body is simply evaluated and then returned. If the body of the lambda is an expression and not a statement, a return is implicit. On the contrary, if the body includes more than one statement, a return must be specified, and it marks return of control back to the caller.

The following code demonstrates a lambda expression that does not contain any arguments:

StringReturn msg = () -> "This is a test";

Let’s take a look at how this lambda expression works. In the previous listing, an object of type StringReturn is returned from the lambda expression. The empty set of parentheses denotes that there are no arguments being passed to the expression. The return is implicit, and the string "This is a test" is returned from the lambda expression to the invoker. The expression in the example is assigned to a variable identified by msg. Assume that the functional interface, StringReturn, contains an abstract method identified as returnMessage(). In this case, the msg.returnMessage() method can be invoked to return the string.

The body of a lambda expression can contain any Java construct that an ordinary method may contain. For instance, suppose a string were passed as an argument to a lambda expression, and you wanted to return some value that is dependent upon the string argument. The following lambda expression body contains a block of code, which returns an int, based upon the string value of the argument passed into the expression.

ActionCode code = (codestr) -> {
switch(codestr){
case "ACTIVE": return 0;
case "INACTIVE": return 1;
default:
return -1;
}
};

In this example, the ActionCode functional interface is used to infer the return type of the lambda expression. For clarification, let’s see what the interface looks like.

interface ActionCode{
int returnCode(String codestr);
}

The code implies that the lambda expression implements the returnCode method, which is defined within the ActionCode interface. This method accepts a string argument (codestr), which is passed to the lambda expression, returning an int. Therefore, from this example you can see that a lambda can encapsulate the functionality of a method body.

Image Note A lambda expression can contain any statement that an ordinary Java method contains. However, the continue and break keywords are illegal at the top level.

6-2. Enabling the Use of Lambda Expressions

Problem

You are interested in authoring code that enables the use of lambda expressions.

Solution 1

Write custom functional interfaces that can be implemented via lambda expressions. All lambda expressions implement a functional interface, a.k.a. an interface with one abstract method definition. The following lines of code demonstrate a functional interface that contains one method definition.

interface ReverseType {
String reverse(String text);
}

The functional interface contains a single abstract method definition, identified as String reverse(String text). The following code, which contains a lambda expression, demonstrates how to implement ReverseType.

ReverseType newText = (testText) -> {
String tempStr = "";
for (String part : testText.split(" ")) {
tempStr = new StringBuilder(part).reverse().toString();
}
return tempStr;
};

The following code could be used to invoke the lambda expression:

System.out.println(newText.reverse("HELLO"));

Result:

OLLEH

Solution 2

Use a functional interface that is contained within the java.util.function package to implement a lambda expression to suit the needs of the application. The following example uses the Function<T,R> interface to perform the same task as the one contained in solution 1. This example accepts a string argument and returns a string result.

Function<String,String> newText2 = (testText) -> {
String tempStr = "";
for (String part : testText.split(" ")) {
tempStr = new StringBuilder(part).reverse().toString();
}
return tempStr;
};

This lambda expression is assigned to the variable newText2, which is of type Function<String,String>. Therefore, a string is passed as an argument, and a string is to be returned from the lambda expression. The functional interface of Function<T,R> contains an abstract method declaration of apply(). To invoke this lambda expression, use the following syntax:

System.out.println(newText2.apply("WORLD"));

Result:

DLROW

How It Works

A basic building block of a lambda expression is the functional interface. A functional interface is a standard Java interface that contains only one abstract method declaration and provides a target type for lambda expressions and method references. A functional interface may contain default method implementations as well, but only one abstract declaration. The abstract method is then implicitly implemented by the lambda expression. As a result, the lambda expression can be assigned to a variable of the same type as the functional interface. The method can be called upon from the assigned variable at a later time, thus invoking the lambda expression. Following this pattern, lambda expressions are method implementations that can be invoked by name. They can also be passed as arguments to other methods (see Recipe 6-8).

Image Note The functional interface in solution 1 contains the @FunctionalInterface annotation. This can be placed on a functional interface to catch compiler-level errors, but it has no effect on the interface itself.

At this point you may be wondering if you will be required to develop a functional interface for each situation that may be suitable for use with a lambda expression. This is not the case, as there are many functional interfaces already in existence. Some examples includejava.lang.Runnable, javafx.event.EventHandler, and java.util.Comparator. See some of the other recipes in this chapter for examples using lambda expressions that implement these interfaces. However, there are also many more functional interfaces that are less specific, enabling them to be tailored to suit the needs of a particular requirement. The java.util.function package contains a number of functional interfaces that can be useful when implementing lambda expressions. The functional interfaces contained within the package are utilized throughout the JDK, and they can also be utilized in developer applications. Table 6-1 lists the functional interfaces that are contained within the java.util.function package, along with a description of each. Note that a Predicate test that returns a Boolean value.

Table 6-1. Functional Interfaces Contained in java.util.function

Interface

Implementation Description

BiConsumer<T,U>

Function operation that accepts two input arguments and returns no result.

BiFunction<T,U,R>

Function that accepts two arguments and produces a result.

BinaryOperator<T>

Function operation upon two operands of the same type, producing a result of the same type as the operands.

BiPredicate<T,U>

Predicate of two arguments. Returns a Boolean value.

BooleanSupplier

Supplier of Boolean-valued results.

Consumer<T>

Function operation that accepts a single input argument and returns no result.

DoubleBinaryOperator

Function operation upon two double-valued operands and producing a double-valued result.

DoubleConsumer

Function operation that accepts a single double-valued argument and returns no result.

DoubleFunction<R>

Function that accepts a double-valued argument and produces a result.

DoublePredicate

Predicate of one double-valued argument.

DoubleSupplier

Supplier of double-valued results.

DoubleToIntFunction

Function that accepts a double-valued argument and produces an int-valued result.

DoubleToLongFunction

Function that accepts a double-valued argument and produces a long-valued result.

DoubleUnaryOperator

Function operation on a single double-valued operand that produces a double-valued result.

Function<T,R>

Function that accepts one argument and produces a result.

IntBinaryOperator

Function operation upon two int-valued operands and producing an int-valued result.

IntConsumer

Function operation that accepts a single int-valued argument and returns no result.

IntFunction<R>

Function that accepts an int-valued argument and produces a result.

IntPredicate

Predicate of one int-valued argument.

IntSupplier

Supplier of int-valued results.

IntToDoubleFunction

Function that accepts an int-valued argument and produces a double-valued result.

IntToLongFunction

Function that accepts an int-valued argument and produces a long-valued result.

IntUnaryOperator

Function operation on a single int-valued operand that produces an int-valued result.

LongBinaryOperator

Function operation upon two long-valued operands and producing a long-valued result.

LongConsumer

Function operation that accepts a single long-valued argument and returns no result.

LongFunction<R>

Function that accepts a long-valued argument and produces a result.

LongPredicate

Predicate of one long-valued argument.

LongSupplier

Supplier of long-valued results.

LongToDoubleFunction

Function that accepts a long-valued argument and produces a double-valued result.

LongToIntFunction

Function that accepts a long-valued argument and produces an int-valued result.

LongUnaryOperator

Function operation on a single long-valued operand that produces a long-valued result.

ObjDoubleConsumer<T>

Function operation that accepts an object-valued and a double-valued argument and returns no result.

ObjIntConsumer<T>

Function operation that accepts an object-valued and an int-valued argument and returns no result.

ObjLongConsumer<T>

Function operation that accepts an object-valued and a long-valued argument and returns no result.

Predicate<T>

Predicate of one argument.

Supplier<T>

Supplier of results.

ToDoubleBiFunction<T,U>

Function that accepts two arguments and produces a double-valued result.

ToDoubleFunction<T>

Function that produces a double-valued result.

ToIntBiFunction<T,U>

Function that accepts two arguments and produces an int-valued result.

ToIntFunction<T>

Function that produces an int-valued result.

ToLongBiFunction<T,U>

Function that accepts two arguments and produces a long-valued result.

ToLongFunction<T>

Function that produces a long-valued result.

UnaryOperator<T>

Function operation on a single operand that produces a result of the same type as its operand.

Utilizing functional interfaces contained within the java.util.function package can greatly reduce the amount of code you need to write. Not only are the functional interfaces geared toward tasks that are performed a high percentage of the time, but they are also written using generics, allowing them to be applied in many different contexts. Solution 2 demonstrates such an example, whereby the Function<T,R> interface is used to implement a lambda expression that accepts a string argument and returns a string result.

6-3. Sorting with Fewer Lines of Code

Problem

Your application contains a list of Player objects for a hockey team. You would like to sort that list of Players by those who scored the most goals, and you would like to do so using terse, yet easy-to-follow code.

Image Note The solutions in this recipe utilize Collections and sorting. To learn more about Collections, refer to Chapter 7.

Solution 1

Create a Comparator using an accessor method contained within the Player object for the field by which you want to sort. In this case, you want to sort by number of goals, so the Comparator should be based upon the value returned from getGoals(). The following line of code shows how to create such a Comparator using the Comparator interface and a method reference.

Comparator<Player> byGoals = Comparator.comparing(Player::getGoals);

Next, utilize a mixture of lambda expressions and streams (See Chapter 7 for full details on streams), along with the forEach() method, to apply the specified sort on the list of Player objects. In the following line of code, a stream is obtained from the list, which allows you to apply functional-style operations on the elements.

team.stream().sorted(byGoals)
.map(p -> p.getFirstName() + " " + p.getLastName() + " - "
+ p.getGoals())
.forEach(element -> System.out.println(element));

Assuming that the List referenced by team is loaded with Player objects, the previous line of code will first sort that list by the Player goals, and then print out information on each object.

Results from the sort:

== Sort by Number of Goals ==
Jonathan Gennick - 1
Josh Juneau - 5
Steve Adams - 7
Duke Java - 15
Bob Smith - 18

Solution 2

Utilize the Collections.sort() method, passing the list to sort along with a lambda expression that performs the comparisons on the list elements. The following code demonstrates how to accomplish this task using the Collections.sort() technique.

Collections.sort(team, (p1, p2)
-> p1.getLastName().compareTo(p2.getLastName()));
team.stream().forEach((p) -> {
System.out.println(p.getLastName());
});

Result:

== Sort by Last Name ==
Adams
Gennick
Java
Juneau
Smith

Image Note This solution could be further simplified if the Player class included a comparison method. If this were the case, a method reference could be used, rather than implementing a lambda expression. For more information regarding method references, see Recipe 6-9.

How It Works

Java 8 introduces some new features that greatly increase developer productivity for sorting collections. Three new features are demonstrated in the solution to this recipe: lambda expressions, method references, and streams. We will look into streams and method references in more detail within other recipes in this book, but we also briefly describe them here to enable the understanding of this recipe. Streams can be applied to collections of data, and they allow enhanced functional-style operations to be applied to the elements within the collections. Streams do not store any data; rather, they enable more functionality on the collections from which they are obtained.

In solution 1, a Comparator is generated, by which the Player objects will be evaluated for the number of goals scored (getGoals). A stream is then generated from a List<Player> that is referenced as team. The stream provides the sorted() function, which accepts aComparator by which to perform a sort on a stream of data. The Comparator that was initially generated is passed to the sorted() function, and then the map() function is called upon the result. The map() function provides the ability to map expressions to each element within the stream. Therefore, within the map, this solution utilizes a lambda expression to create a string that contains each Player object’s firstName, lastName, and goals fields. Lastly, since the List<Player> is an iterable, it contains the forEach() method. The forEach() method enables an expression or group of statements to be applied to each element within the list. In this case, each element in the list is printed to the command line. As such, since the map() function was applied to the stream, each element in the list is subsequently printed per the algorithm applied within the map(). Therefore, the result is that the players first and last names along with the number of goals each has scored will be printed at the command line.

Solution 2 uses a different technique to accomplish a similar task. In the second solution, the Collections.sort() method is invoked on the list. The first argument to Collections.sort() is the list itself, and the second argument is the comparison implementation in the form of a lambda expression. The lambda expression in this case has two parameters passed to it, both Player objects, and it compares the lastName of the first player to the lastName of the second player. Therefore, the sort will be performed on the lastName field of the Player object, in ascending order. To finish off solution 2, the sorted list is printed out. To do this a stream is generated from the sorted list, and the forEach() method is then invoked on the stream of data, printing out each player’s lastName.

No doubt, the lambda expression greatly reduces the amount of code required to sort collections of data. It also makes it easy to understand the logic behind the sort, as readability is much easier than trying to follow looping implementations of the past. For more examples on using lambdas with collections of data, see Chapter 7.

6-4. Specifying Filter Criteria on a Collection of Data

Problem

You have a list of data to which you’d like to apply some filtering so that you can extract objects meeting the specified filter criteria.

Solution

Create a stream from the list of data and apply a filter, passing the desired predicate. Finally, add each of the objects matching the specified filter criteria to a new list. In the following example, a list of Player objects is being filtered to capture only those players who have scored ten or more goals.

team.stream().filter(
p -> p.getGoals() >= 10
&& p.getStatus() == 0)
.forEach(element -> gteTenGoals.add(element));
System.out.println("Number of Players Matching Criteria: " + gteTenGoals.size());

How It Works

The solution to this recipe makes use of a data stream since it contains an easy-to-use filter function. The collection of data, team, generates a stream, and then the filter function is called upon it, accepting a predicate by which to filter the data within the collection. The predicate is written in the form of a lambda expression that contains two such filtering criteria. The lambda expression passes a Player object as an argument, and then filters the data based upon the number of goals being greater than or equal to ten and an active status.

Once the data has been filtered, the forEach() method is used to add each of the elements that meet the filtering criteria to a list. This is also done using a lambda expression. The element to be added to the list is passed to the lambda expression as an argument, and it is subsequently added to the list within the body of the expression.

Lambda expressions are very well suited for working within stream functions. Not only do they enable easier development of business logic, but they also make collections filtering easier to read and maintain.

6-5. Implementing Runnable

Problem

You would like to create a runnable piece of code in a terse manner.

Solution

Utilize a lambda expression to implement the java.util.Runnable interface. The java.util.Runnable interface is a perfect match for lambda expressions since it contains only a single abstract method, run(). In this solution, we will compare the legacy technique, creating a new Runnable, and the new technique using a lambda expression.

The following lines of code demonstrate how to implement a new Runnable piece of code using the legacy technique.

Runnable oldRunnable = new Runnable() {
@Override
public void run() {
int x = 5 * 3;
System.out.println("The variable using the old way equals: " + x);
}
};

Now take a look at how this can be written using a lambda expression instead.

Runnable lambdaRunnable = () -> {
int x = 5 * 3;
System.out.println("The variable using the lambda equals: " + x);
};

oldRunnable.run();
lambdaRunnable.run();

As you can see, the legacy procedure for implementing a Runnable takes a few more lines of code than implementing Runnable with a lambda expression. The lambda expression also makes the Runnable implementation easier to read and maintain.

How It Works

Since java.util.Runnable is a functional interface, the boilerplate of implementing the run() method can be abstracted away using a lambda expression. The general format for implementing a Runnable with a lambda expression is as follows:

Runnable assignment = () -> {expression or statements};

A Runnable can be implemented by using a zero-argument lambda expression containing an expression or a series of statements within the lambda body. The key is that the implementation takes no arguments and returns nothing.

6-6. Replacing Anonymous Inner Classes

Problem

Portions of your code contain anonymous inner classes, which are sometimes difficult to follow. You would like to replace anonymous inner classes with code that is easier to read and maintain.

Solution

Replace the anonymous inner classes with lambda expressions. By doing so, development time will be much faster as there will be fewer lines of boilerplate code required. A typical JavaFX application utilizes anonymous inner classes to add functionality to application constructs. For instance, anonymous classes are a great way to add an action to a button. The problem is that inner classes can be difficult to follow, and they contain lots of boilerplate code.

The following lines of code demonstrate a typical anonymous inner class implementation for a button action implementation. Let’s look at these lines of code before taking a look at how you can achieve the same solution using a lambda expression.

Button btn = new Button();
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent e) {
createPlayer(firstName.getText(),
lastName.getText(),
Integer.valueOf(goals.getText()),
listView.getSelectionModel().getSelectedItem().toString(),
0);
message.setText("Player Successfully Added");
System.out.println("Player added.");
System.out.println("== Current Player List==");
for (Player p : team) {
System.out.println(p.getFirstName() + " " + p.getLastName());
}
}
});

The same event handler can be implemented using a lambda expression, resulting in an easier-to-read implementation that can be achieved in fewer lines of code.

Button btn = new Button();
btn.setText("Enter Player");
btn.setOnAction(e -> {
createPlayer(firstName.getText(),
lastName.getText(),
Integer.valueOf(goals.getText()),
listView.getSelectionModel().getSelectedItem().toString(),
0);
message.setText("Player Successfully Added");
System.out.println("Player added.");
System.out.println("== Current Player List==");
for (Player p : team) {
System.out.println(p.getFirstName() + " " + p.getLastName());
}
});

How It Works

A great use-case for lambda expressions is that they are very well suited for taking the place of many anonymous class implementations. Most anonymous inner classes implement a functional interface, which makes them perfect candidates for replacement via lambda expressions. In the solution, the anonymous inner class for supporting a JavaFX button action has been redesigned to work within the context of a lambda expression. Since the an EventHandler must implement one abstract method, handle(), it becomes a good fit for a lambda implementation.

In the solution, the EventHandler lambda expression accepts an argument, whose type is derived from the context of the expression. In this case, since the expression is implementing an EventHandler, the derived type for the argument is ActionEvent. The body of the lambda expression contains several lines of code and returns nothing to the caller, as the handle() method contains a void return type.

Although the lambda expression solution does not save more than a few lines of code, it does help increase readability and maintainability. Although anonymous inner classes are an acceptable solution, code that is riddled with such constructs can be cumbersome to work with. Replacing anonymous inner classes with lambda expressions helps to maintain succinct code that is easy to follow.

6-7. Accessing Class Variables from a Lambda Expression

Problem

The class you are writing contains instance variables, and you would like to make them available for use via a lambda expression within the class.

Solution

Make use of instance variables that are contained in enclosing classes, as needed, from within lambda expressions. In the following class, the lambda expression contained within the VariableAccessInner.InnerClass.lambdaInMethod() method can access all enclosing class instance variables. Thus, it is able to print out the VariableAccessInner CLASSA variable, if needed.

public class VariableAccessInner {

public String CLASSA = "Class-level A";

class InnerClass {

public String CLASSA = "Class-level B";

void lambdaInMethod(String passedIn) {
String METHODA = "Method-level A";

Consumer<String> l1 = x -> {
System.out.println(x);
System.out.println("CLASSA Value: " + CLASSA);
System.out.println("METHODA Value: " + METHODA);
};

l1.accept(CLASSA);
l1.accept(passedIn);

}
}
}

Now, let’s execute lambdaInMethod using the following code:

VariableAccessInner vai = new VariableAccessInner();
VariableAccessInner.InnerClass inner = vai.new InnerClass();
inner.lambdaInMethod("Hello");

Result:

Class-level B
CLASSA Value: Class-level B
METHODA Value: Method-level A
Hello
CLASSA Value: Class-level B
METHODA Value: Method-level A

Image Note The CLASSA variable is overridden by a variable using the same identifier within the InnerClass class. Therefore, the CLASSA instance variable that belongs to VariableAccessInner is not printed from within the lambda expression.

How It Works

Lambda expressions have access to the variables located within the enclosing class. Thus, a lambda expression contained within a method of a class can access any instance variables of the enclosing class. There is no additional scope added to a lambda expression, so it can access fields, methods, and local variables of the enclosing scope. In the solution, the lambda expression contained within the lambdaInMethod() method can access all of the fields that are declared within either class. This is because both the inner class and its outer class enclose the lambda. One thing to note is that if an inner class contains an instance variable of the same name as a variable that has been declared in the outer class, then the lambda will use the variable of its enclosing class. Therefore, in the solution, the InnerClass CLASSA field is accessed from within the lambda expression, rather than the outer class reference.

Local variables that are referenced from within a lambda expression must be either final or effectively final. Therefore, if a lambda expression attempts to access a variable that has been changed within the context of an enclosing method, an error will occur. For instance, suppose that the method in the solution were changed to the following:

void lambdaInMethod(String passedIn) {
String METHODA = "Method-level A";
passedIn = "test";
Consumer<String> l1 = x -> {
System.out.println(x);
System.out.println("CLASSA Value: " + CLASSA);
System.out.println("METHODA Value: " + METHODA);
System.out.println(passedIn);
};

l1.accept(CLASSA);
l1.accept(passedIn);

}

Note that the string that is passed into lambdaInMethod() is assigned a new value just before the lambda expression is invoked. Therefore, the passedIn variable is no longer effectively final, and lambda expressions cannot introduce a new level of scope. Consequently, the lambda expression does not have access to the passedIn variable from within the context of the expression.

6-8. Passing Lambda Expressions to Methods

Problem

A lambda expression has been created to encapsulate some functionality. You would like to take that functionality and pass it into a method as an argument, so that the method implementation can take advantage of the expression.

Solution

Create portable functions using lambda expressions by implementing a functional interface and then assigning the lambda expression to a variable of the same type as the interface. The variable can be passed to other objects as an argument.

The following class, PassingLambdaFunctions, contains a calculate() method, which will be used to perform calculations of any type given an array of values. Note that the calculate() method accepts a Function<List<Double>,Double> and an array ofDouble values as arguments.

public class PassingLambdaFunctions {
/**
* Calculates a value based upon the calculation function that is passed
* in.
* @param f1
* @param args
* @param x
* @param y
* @param z
* @return
*/
public Double calculate(Function<List<Double>, Double> f1,
Double [] args){
Double returnVal;
List<Double> varList = new ArrayList();
int idx = 0;
while (idx < args.length){
varList.add(args[idx]);
idx++;
}
returnVal=f1.apply(varList);

return returnVal;
}
}

To make use of the calculate method, a lambda expression that implements Function<List<Double>,Double> must be passed as the first argument to the calculate() method, along with an array of Double arguments that contains the value to be used within the calculation. In the following class, a function for calculating volume is generated using a lambda expression, and it is assigned to variable identified as volumeCalc of type Function<List<Double>,Double>. Another lambda expression is used to create a function for calculating area, and it is assigned to a variable of the same type, identified as areaCalc. In separate calls, these variables are then passed to the PassingLambdaFunctions.calculate() method, along with an array of values, resulting in the calculated answer.

public class MainClass {
public static void main(String[] args){

double x = 16.0;
double y = 30.0;
double z = 4.0;

// Create volume calculation function using a lambda. The calculator
// checks to ensure that the array contains the three necessary elements
// for the calculation.
Function<List<Double>, Double> volumeCalc = list -> {
if(list.size() == 3){
return list.get(0) * list.get(1) * list.get(2);
} else {
return Double.valueOf("-1");
}
};
Double[] argList = new Double[3];
argList[0] = x;
argList[1] = y;
argList[2] = z;

// Create area calculation function using a lambda. This particular
// calculator checks to ensure that the array only contains two elements.
Function<List<Double>, Double> areaCalc = list -> {
if(list.size() == 2){
return list.get(0) * list.get(1);
} else {
return Double.valueOf("-1");
}
};
Double[] argList2 = new Double[2];
argList2[0] = x;
argList2[1] = y;

PassingLambdaFunctions p1 = new PassingLambdaFunctions();

// Pass the lambda expressions to the calculate() method, along with the
// argument lists.
System.out.println("The volume is: " + p1.calculate(volumeCalc, argList));
System.out.println("The area is: " + p1.calculate(areaCalc, argList2));
}
}

Result:

The volume is: 1920.0
The area is: 480.0

How It Works

Lambda expressions can be assigned to variables of the same type as the functional interface being implemented. Such expressions can contain a single-line expression or a multi-statement body. Since the lambda expression can accept arguments, there are use cases for assigning such expressions to variables and then passing those variables into other objects to modify functionality. This pattern is useful for creating solutions that may contain more than one implementation. The solution to this recipe demonstrates this concept.

In the solution, a class named PassingLambdaFunctions contains a single method identified as calculate(). The calculate() method is to be used for performing calculations on Double values that are passed into it as arguments. However, the calculate() method contains no calculation functionality at all. Rather, the calculation functionality is passed into it as an argument of type Function<List<Double>,Double> via a lambda expression. This type is actually one of the standard functional interfaces contained within thejava.util.function package (see Recipe 6-2), and the interface can be implemented by lambda expressions and then invoked at a later time by calling its solo apply() method. Looking at the code in the calculate() method, the arguments contained within the Double[] are first added to a list. Next, lambda expression’s apply() method is invoked, passing the new list of values, and returning a result into returnVal. Finally, returnVal is returned to the method invoker.

returnVal=f1.apply(varList);
return returnVal;

To implement the calculation functionality within the solution, lambda expressions are created in a separate class named MainClass. Each expression accepts a list of arguments and then performs a calculation on the values in the list, returning a result. For instance, the first lambda generated in the MainClass calculates volume by multiplying together all of the values contained in the argument list and returns the result. This functionality is then assigned to a variable of type Function<List<Double>,Double>, and then it is passed into thePassingLambdaFunctions.calculate() method later on.

Any type of functionality can be implemented within a lambda expression and then passed around to different objects for use. This is an excellent way to promote code reuse and high maintainability.

6-9. Invoking Existing Methods by Name

Problem

You are developing a lambda expression that merely invokes a method that already exists in the object being passed to the lambda.

Solution

Use a method reference, rather than writing a lambda expression, to call an existing method. In the following scenario, the Player object contains a static method named compareByGoals(), which takes two Player objects and compares the number of goals each contains. It then returns an integer representing the outcome. For all intents and purposes, the compareByGoals() method is the same as a Comparator.

public class Player {

private String firstName = null;
private String lastName = null;
private String position = null;
private int status = -1;
private int goals;

public Player(){

}

public Player(String position, int status){
this.position = position;
this.status = status;
}

protected String playerStatus(){
String returnValue = null;

switch(getStatus()){
case 0:
returnValue = "ACTIVE";
case 1:
returnValue = "INACTIVE";
case 2:
returnValue = "INJURY";
default:
returnValue = "ON_BENCH";
}

return returnValue;
}

public String playerString(){
return getFirstName() + " " + getLastName() + " - " + getPosition();
}

// ** getters and setters removed for brevity **

/**
* Returns a positive integer if Player A has more goals than Player B
* Returns a negative integer if Player A has fewer goals than Player B
* Returns a zero if both Player A and Player B have the same number of goals
* @param a
* @param b
* @return
*/
public static int compareByGoal(Player a, Player b){
int eval;
if(a.getGoals() > b.getGoals()){
eval = 1;
} else if (a.getGoals() < b.getGoals()){
eval = -1;
} else {
eval = 0;
}
return eval;
}

}

The Player.compareByGoal() method could be used to sort an array of Player objects. To do so, pass an array of Player objects (Player[]) to the Arrays.sort() method as the first argument, and pass a method reference Player::compareByGoal as the second argument. The result will be a sorted list (in ascending order) of Player objects by number of goals. The following line of code shows how to accomplish this task.

Arrays.sort(teamArray, Player::compareByGoal);

How It Works

Consider that your lambda expression is going to invoke a single method by name, perhaps returning a result. If a lambda expression fits this scenario, it is a prime candidate for use with a method reference. A method reference is a simplified form of a lambda expression, which specifies the class name or instance name, followed by the method to be called in the following format:

<class or instance name>::<methodName>

The double colon (::) operator specifies a method reference. Since a method reference is a simplified lambda method, it must implement a functional interface, and the abstract method within the interface must have the same argument list and return type as the method being referenced. Any arguments are subsequently derived from the context of the method reference. For instance, consider the same scenario as the solution, whereby you wanted to sort an array of Player objects by calling upon the Player.compareByGoal() method to perform goal comparisons. The following code could be written to enable this functionality via a lambda expression:

Arrays.sort(teamArray, (p1, p2) -> Player.compareByGoal(p1,p2));

In this code, the array is passed as the first argument to Arrays.sort(), and the second argument is a lambda expression that passes two Player objects to the Player.compareByGoal() method. The lambda expression uses the functional interfaceComparator<Player>.compare, which utilizes the (Player, Player) parameter list. The compareByGoal() method contains that same parameter list. Likewise, the return type of compareByGoal() matches the return type within the functional interface. Therefore, the parameter list does not need to be specified in the listing; it can be inferred from the context of the method reference Player::compareByGoal instead.

There are four different types of method references, and Table 6-2 lists each of them.

Table 6-2. Method Reference Types

Type

Description

Static Reference

Uses a static method of an object.

Instance Reference

Uses an instance method of an object.

Arbitrary Object Method

Used on an arbitrary object of a particular type, rather than a particular object.

Constructor Reference

Used to generate a new object by invoking a constructor with the new keyword.

In the solution, the static method reference type is demonstrated since compareByGoal() is a static method within a static class. It is possible to invoke a method of an object instance using an instance reference. Consider the following class, which contains a non-static method for comparing goals within Player objects.

public class PlayerUtility {

public int compareByGoal(Player a, Player b){
int eval;
if(a.getGoals() > b.getGoals()){
eval = 1;
} else if (a.getGoals() < b.getGoals()){
eval = -1;
} else {
eval = 0;
}
return eval;
}
}

This class can be instantiated, and the new instance can be used to reference the compareByGoals() method, similarly to the technique that was used in the solution to this recipe.

Player[] teamArray2 = team.toArray(new Player[team.size()]);
PlayerUtility utility = new PlayerUtility();
Arrays.sort(teamArray2, utility::compareByGoal);

Suppose that your application contained a list of an arbitrary type, and you wanted to apply a method to each of the objects in that list. Method references can be used in this scenario, given the object contains methods that are candidates for use via reference. In the following example, theArrays.sort() method is applied to a list of int values, and a method reference is used to apply the Integer compare() method to the elements within the list. Thus, the resulting list will be sorted, and the method reference automatically passes the int arguments and returns theint comparison.

Integer[] ints = {3,5,7,8,51,33,1};
Arrays.sort(ints, Integer::compare);

The last type of method reference can be utilized for referencing the constructor of an object. This type of method reference can be especially useful when creating new objects via a factory. Let’s take a look at an example. Suppose that the Player object contained the following constructor:

public Player(String position, int status, String first, String last){
this.position = position;
this.status = status;
this.firstName = first;
this.lastName = last;
}

You are interested in generating Player objects on-the-fly, using a factory pattern. The following code demonstrates an example of a functional interface containing a single abstract method named createPlayer(), which accepts the same argument list as the constructor for thePlayer object.

public interface PlayerFactory {
Player createPlayer(String position,
int status,
String firstName,
String lastName);
}

The factory can now be created from a lambda expression, and then called upon to create new objects. The following lines of code demonstrate:

PlayerFactory player1 = Player::new;
Player newPlayer = player1.createPlayer("CENTER", 0, "Constructor", "Referenceson");

Method references are perhaps one of the most significant new features of Java 8, although lambda expressions have more use-cases. They provide an easy-to-read, simplified technique for generating lambda expressions, and they’ll work in most cases where a lambda is merely invoking a single method by name.

Summary

It is not very often that a new construct added to a language can have as large of an impact as lambda expressions to Java. For years, developers have been utilizing such constructs as anonymous inner classes to add subtle functionality to applications. With the addition of lambda expressions, that subtle functionality can be developed with easy-to-read code, rather than redundant and difficult-to-read boilerplate code. Moreover, many languages today make it possible to pass functional pieces of code around, dynamically altering the functionality of existing code. Such solutions are now available in the Java language, allowing developers to make use of more modern programming techniques.

Lambda expressions bring new life to the Java language, providing capabilities that Java developers have not had in the past. Developers of desktop, mobile, and enterprise applications alike will be able to take advantage of the lambda expression to create more robust and sophisticated solutions. Lambda expressions are a revolutionary change to the language, and they have a significant impact on development across the platform.