Introducing
Lambda Expressions
Key to understanding Java’s
implementation of lambda expressions are two constructs. The first is the
lambda expression, itself. The second is the functional interface. Let’s begin
with a simple definition of each.
A lambda expression is, essentially, an anonymous (that is, unnamed)
method. However, this method is not executed on its own. Instead, it is used to
implement a method defined by a functional interface. Thus, a lambda expression
results in a form of anonymous class. Lambda expressions are also commonly
referred to as closures.
A functional interface is an interface that contains one and only one
abstract method. Normally, this method specifies the intended purpose of the
interface. Thus, a functional interface typically represents a single action.
For example, the standard interface Runnable
is a functional interface because it defines only one method: run( ). Therefore, run( ) defines the action of Runnable.
Furthermore, a functional interface defines the target type of a lambda expression. Here is a key point: a lambda
expression can be used only in a context in which its target type is specified.
One other thing: a functional interface is sometimes referred to as a SAM type, where SAM stands for Single
Abstract Method.
Lambda
Expression Fundamentals
The lambda expression
introduces a new syntax element and operator into the Java language. The new
operator, sometimes referred to as the lambda
operator or the arrow operator,
is −>. It divides a lambda
expression into two parts. The left side specifies any parameters required by
the lambda expression. (If no parameters are needed, an empty parameter list is
used.) On the right side is the lambda
body, which specifies the actions of the lambda expression.
The −> can be verbalized as “becomes” or “goes to.”
Java defines two types of
lambda bodies. One consists of a single expression, and the other type consists
of a block of code. We will begin with lambdas that define a single expression.
Lambdas with block bodies are discussed later in this chapter.
At this point, it will be
helpful to look a few examples of lambda expressions before continuing. Let’s
begin with what is probably the simplest type of lambda expression you can
write. It evaluates to a constant value and is shown here:
() -> 123.45
This lambda expression takes
no parameters, thus the parameter list is empty. It returns the constant value
123.45. Therefore, it is similar to the following method:
double myMeth() { return 123.45; }
Of course, the method defined
by a lambda expression does not have a name. A slightly more interesting lambda
expression is shown here:
() -> Math.random() * 100
This lambda expression
obtains a pseudo-random value from Math.random(
), multiplies it by 100, and returns the result. It, too, does not require
a parameter.
When a lambda expression
requires a parameter, it is specified in the parameter list on the left side of
the lambda operator. Here is a simple example:
(n) -> (n % 2)==0
This lambda expression
returns true if the value of
parameter n is even. Although it is
possible to explicitly specify the type of a parameter, such as n in this case, often you won’t need to
do so because in many cases its type can be inferred. Like a named method, a
lambda expression can specify as many parameters as needed.
Functional
Interfaces
As stated, a functional
interface is an interface that specifies only one abstract method. If you have
been programming in Java for some time, you might at first think that all
interface methods are implicitly abstract. Although this was true prior to JDK
8, the situation has changed. As explained in Chapter 9, beginning with JDK 8,
it is possible to specify default behavior for a method declared in an
interface. This is called a default
method. Today, an interface method is abstract only if it does not specify
a default implementation. Because nondefault interface methods are implicitly
abstract, there is no need to use the abstract
modifier (although you can specify it, if you like).
Here is an example of a
functional interface:
interface MyNumber { double getValue();
}
In this case, the method getValue( ) is implicitly abstract, and
it is the only method defined by MyNumber.
Thus, MyNumber is a functional
interface, and its function is defined by getValue(
).
As mentioned earlier, a
lambda expression is not executed on its own. Rather, it forms the
implementation of the abstract method defined by the functional interface that
specifies its target type. As a result, a lambda expression can be specified
only in a context in which a target type is defined. One of these contexts is
created when a lambda expression is assigned to a functional interface
reference. Other target type contexts include variable initialization, return statements, and method
arguments, to name a few.
Let’s work through an example
that shows how a lambda expression can be used in an assignment context. First,
a reference to the functional interface MyNumber
is declared:
// Create a reference to a MyNumber instance.
MyNumber myNum;
Next, a lambda expression is
assigned to that interface reference:
// Use a lambda in an assignment context.
myNum = () -> 123.45;
When a lambda expression
occurs in a target type context, an instance of a class is automatically
created that implements the functional interface, with the lambda expression
defining the behavior of the abstract method declared by the functional
interface. When that method is called through the target, the lambda expression
is executed. Thus, a lambda expression gives us a way to transform a code
segment into an object.
In the preceding example, the
lambda expression becomes the implementation for the getValue( ) method. As a result, the following displays the value
123.45:
//Call getValue(), which is implemented by the
previously assigned lambda expression.
System.out.println("myNum.getValue());
Because the lambda expression
assigned to myNum returns the value
123.45, that is the value obtained when getValue(
) is called.
In order for a lambda
expression to be used in a target type context, the type of the abstract method
and the type of the lambda expression must be compatible. For example, if the
abstract method specifies two int
parameters, then the lambda must specify two parameters whose type either is
explicitly int or can be implicitly
inferred as int by the context. In
general, the type and number of the lambda expression’s parameters must be
compatible with the method’s parameters; the return types must be compatible;
and any exceptions thrown by the lambda expression must be acceptable to the
method.
Some
Lambda Expression Examples
With the preceding discussion
in mind, let’s look at some simple examples that illustrate the basic lambda
expression concepts. The first example puts together the pieces shown in the
foregoing section.
//Demonstrate a simple lambda expression.
//A functional interface.
interface MyNumber { double getValue();
}
class LambdaDemo {
public static void main(String args[])
{
MyNumber myNum; // declare an interface reference
//Here, the lambda expression is simply a
constant expression.
//When it is assigned to myNum, a class
instance is constructed in which the lambda expression implements the
getValue() method in MyNumber.
myNum = () -> 123.45;
//Call getValue(), which is provided by the
previously assigned lambda expression.
System.out.println("A fixed value: "
+ myNum.getValue());
//Here, a more complex expression is used.
myNum = () -> Math.random() * 100;
//These call the lambda expression in the
previous line.
System.out.println("A random value: "
+ myNum.getValue()); System.out.println("Another random value: " +
myNum.getValue());
//A lambda expression must be compatible with
the method defined by the functional interface. Therefore, this won't work:
myNum = () -> "123.03"; // Error!
}
}
Sample output from the program
is shown here:
A fixed value: 123.45
A random value: 88.90663650412304
Another random value: 53.00582701784129
As mentioned, the lambda
expression must be compatible with the abstract method that it is intended to
implement. For this reason, the commented-out line at the end of the preceding
program is illegal because a value of type String
is not compatible with double, which
is the return type required by getValue(
).
The next example shows the
use of a parameter with a lambda expression:
//Demonstrate a lambda expression that takes a
parameter.
//Another functional interface.
interface NumericTest { boolean test(int n);
}
class LambdaDemo2 {
public static void main(String args[])
{
// A lambda expression that tests if a number
is even.
NumericTest isEven = (n) -> (n % 2)==0;
if(isEven.test(10)) System.out.println("10
is even");
if(!isEven.test(9)) System.out.println("9
is not even");
//Now, use a lambda expression that tests if a
number is non-negative.
NumericTest isNonNeg = (n) -> n >= 0;
if(isNonNeg.test(1)) System.out.println("1
is non-negative");
if(!isNonNeg.test(-1))
System.out.println("-1 is negative");
}
}
The output from this program
is shown here:
10 is even
9 is not even
1 is non-negative -1 is negative
This program demonstrates a
key fact about lambda expressions that warrants close examination. Pay special
attention to the lambda expression that performs the test for evenness. It is
shown again here:
(n) -> (n % 2)==0
Notice that the type of n is not specified. Rather, its type is
inferred from the context. In this case, its type is inferred from the
parameter type of test( ) as defined
by the NumericTest interface, which
is int. It is also possible to
explicitly specify the type of a parameter in a lambda expression. For example,
this is also a valid way to write the preceding:
(int n) -> (n % 2)==0
Here, n is explicitly specified as int.
Usually it is not necessary to explicitly specify the type, but you can in
those situations that require it.
This program demonstrates
another important point about lambda expressions: A functional interface
reference can be used to execute any lambda expression that is compatible with
it. Notice that the program defines two different lambda expressions that are
compatible with the test( ) method
of the functional interface NumericTest.
The first, called isEven, determines
if a value is even. The second, called isNonNeg,
checks if a value is non-negative. In each case, the value of the parameter n is tested. Because each lambda expression
is compatible with test( ), each can
be executed through a NumericTest
reference.
One other point before moving
on. When a lambda expression has only one parameter, it is not necessary to
surround the parameter name with parentheses when it is specified on the left
side of the lambda operator. For example, this is also a valid way to write the
lambda expression used in the program:
n -> (n % 2)==0
For consistency, this book
will surround all lambda expression parameter lists with parentheses, even
those containing only one parameter. Of course, you are free to adopt a
different style.
The next program demonstrates
a lambda expression that takes two parameters. In this case, the lambda
expression tests if one number is a factor of another.
// Demonstrate a lambda expression that takes
two parameters.
interface NumericTest2 { boolean test(int n,
int d);
}
class LambdaDemo3 {
public static void main(String args[])
{
// This lambda expression determines if one
number is a factor of another.
NumericTest2 isFactor = (n, d) -> (n % d) ==
0;
if(isFactor.test(10, 2))
System.out.println("2 is a factor of 10");
if(!isFactor.test(10, 3))
System.out.println("3 is not a factor of
10");
}
}
The output is shown here:
2 is a factor of 10
3 is not a factor of 10
In this program, the
functional interface NumericTest2
defines the test( ) method:
boolean test(int n, int d);
In this version, test( ) specifies two parameters. Thus,
for a lambda expression to be compatible with test( ), the lambda expression must also specify two parameters.
Notice how they are specified:
(n, d) -> (n % d) == 0
The two parameters, n and d, are specified in the parameter list, separated by commas. This
example can be generalized. Whenever more than one parameter is required, the
parameters are specified, separated by commas, in a parenthesized list on the
left side of the lambda operator.
Here is an important point
about multiple parameters in a lambda expression: If you need to explicitly
declare the type of a parameter, then all of the parameters must have declared
types. For example, this is legal:
(int n, int d) -> (n % d) == 0
But this is not:
(int n, d) -> (n % d) == 0
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.