Chapter: Java The Complete Reference - The Java Language - Lambda Expressions

| Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail |

Introducing Lambda Expressions

Lambda Expression Fundamentals, Some Lambda Expression Examples, Functional Interfaces.

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


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail


Copyright © 2018-2020 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.