Java 11 Developer Certification - Lambda Expressions
April 07, 2021
What we are covering in this lesson
- Genesis of Lambda Expressions
- Anonymous Classes
- Functional Interfaces
- Lambda Expression Syntax
- Parameter Declarations Syntax
- Body Declaration Syntax
Genesis of Lambda Expressions
To understand what is lambda expression, we must first understand the origin of it. And I am sure people working on anything less than Java 8 would totally be able to relate to it :)
Before lambda expressions, anonymous inner classes were the feature that enabled you to make your code more concise. They did, and still do, enable you to declare and instantiate a class at the same time, and use methods on that class, all within the same segment of code. Anonymous classes are like local classes, except that they do not have a name. You’ll use them if you need to use a local class only once.
One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of using an anonymous class may seem unwieldy and unclear. Lambda expressions replace the bulkiness of anonymous inner classes.
Lets look at a simple example to deep dive into anonymous classes.
Anonymous Classes
Here we will look at a very simple java code using anonymous class.
In the main method, we are creating a local inner class LocalClass which extends Object and defines a method hello.
In comparision, we are also creating an anonymous class anomClass of type Object, which overrides the toString method on Object.
Then we are executing both methods, and as you can see the local class needs instantiation (using the new operator) and then we call the method. The anonymous class on the other hand allows you to just reference its method directly.
We also have an Interface Helloable, and an anonymous class anomInterfacedClass which implements this interface.
Do remember, when we are creating an anonymous class, its a statement and NOT a declaration.
The printHello class takes a Helloable type parameter and calls its hello method.
We are also passing the anonymous class as a parameter to a method which we are actually calling printHello. So what we are doing there is making a call to printHello method and the parameter is an anonymous class.
This looks simple but doesnt look pretty. So we have used the lambda expression at the end!
The lambda expression is possible here because of the interface parameter. We will look at the syntax of lambda expressions in the sections below, but for now we’ll keep in mind that a lambda expression is shorthand for calling a method on an anonymous class using a functional interface.
package maxCode.online.lambdaExpressions;
public class AnonymousTest {
interface Helloable {
public void hello();
}
public static void main(String[] args) {
AnonymousTest a = new AnonymousTest();
// We declare a local inner class (named) in this method
class LocalClass extends Object {
public void hello() {
System.out.println("Hello Local Class");
}
}
// We immediately execute a method on the local class
// If this were the only line of code that used the local class,
// An anonymous class would make more sense.
new LocalClass().hello();
// We create an anonymous class as a statement, not a declaration.
// This one is of type Object (extends Object)
Object anomClass = new Object() {
public String toString() {
return "Hello Anonymous Class";
}
};
// We immediately execute a method on anonymous class
System.out.println(anomClass.toString());
// Anonymous class is a statement, not a declaration.
// This one is a Helloable (implements Helloable)
Helloable anomInterfacedClass = new Helloable() {
public void hello() {
System.out.println("Hello Anonymous Class" + " implementing Interface");
}
};
// Executing methods on anonymous class
anomInterfacedClass.hello();
// In the code below, we actually pass an anonymous class in the
// call to a method that accepts a Helloable type as a parameter
a.printHello(
// Anonymous class created on the fly
new Helloable() {
public void hello() {
System.out.println("Hello Anonymous Class " + "passed as a parameter");
}
});
// In the code below, we now replace the anonymous class parameter
// with a lambda expression
a.printHello(
// Lambda Expression
() -> System.out.println("Hello Lambda Expression" + " as a parameter"));
}
public void printHello(Helloable h) {
h.hello();
}
}
Output
Hello Local Class
Hello Anonymous Class
Hello Anonymous Class implementing Interface
Hello Anonymous Class passed as a parameter
Hello Lambda Expression as a parameter
Functional Interfaces
I am sure we all would unanimously agree that writing less code feels good, and eliminating the clanky constructs after finding a class than having to execute a method on it for simple functions might be an advantage.
The helloable interface in these examples meets the requirements for a functional interface.
From the Oracle Java specification, functional interfaces are described as follows.
Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the functional method for that functional interface, to which the lambda expression’s parameter and return types are matched or adapted. Functional interfaces can provide a target type in multiple contexts, such as assignment context, method invocation, or cast context.
So in other words, any where you can use an interface type in code you can replace the instantiated object which might have been used with a lambda expression.
Lambda Expression Syntax
We will look at the lambda expression syntax here, but to read in more detail, you can go to the below links.
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#syntax https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html
The basic syntax for lambda expressions is as per below table
Argument List | Arrow Token | Body |
---|---|---|
(int a, int b) | -> | x+y |
So a lambda expression is composed of three parts.
The first part is the argument list or parameters This is a comma-separated list of formal parameters enclosed in parentheses.
- You can omit the data type of the parameters in a lambda expression.
- If you include data types, you must declare a data type for each parameter.
- You can omit the paranthesis if there is only 1 parameter.
- If there are no parameters, it is represented by an empty parenthesis set ().
e.g. p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 21 && p.getAge <=30
The second part of the lambda expression is the arrow token. That’s a dash and a greater than sign.
The third part is the body. It can consist of a single expression
- If we specify a single expression, then the Java Runtime evaluates the expressions and then returns its value.
e.g. p -> p.getName() == "Max"
- You do not have to enclose a void method invocation in braces
e.g. s -> System.out.println(s)
- The body can consist of a statement block. A return statement is not an expression; in a lambda expression you must enclose statements in braces {} as below
p -> {
return Calculate.add(4,5);
}
Note that lambda expressions resemble method declarations. In fact you can consider lambda expressions to be anonymous methods, methods without a name, and no longer requiring an anonymous class to wrap them.
Sometimes a lambda expression does nothing but call an existing method. In these cases, you can use a method reference - they’re compact, easy to read lambda expressions for methods that already have a name.
Syntax for method reference
System.out::println
A method reference can completely replace a lambda expression with an arrow token, if the method referred to meets the requirements for the functional interface method.
Parameter Declarations Syntax
Below are some of the VALID lambda expression parameter declaration syntax
Valid Lambda Expressions | Notes | Parenthesis in the Parameter List Declaration |
---|---|---|
() -> System.out.println() | No parameter syntax, () required | Required |
a -> a + 1 | Single parameter syntax, No paranthesis required | Optional |
(a) -> a + 1 | Single parameter syntax, Parenthesis valid but optional | Optional |
(int a) -> a + 1 | Single parameter syntax specifying data type | Required |
(var a) -> a + 1 | Single parameter syntax with Local Variable Type Inference | Required |
(int a, int b) -> a + b | Multiple parameter syntax with declared types. All parameters must specify a type | Required |
(a, b) -> a + b | Multiple parameter syntax with no types. If datatype is not specified for one parameter, it must be ommitted for all other parameters as well | Required |
(var a, var b) -> a + b | Multiple parameter syntax with Local Variable Type Inference. You must use var for all parameter if used for one | Required |
Now lets look at some of the syntactically incorrect syntax. Below table lists examples of syntactially incorrect parameter declaration in lambda expression.
Invalid Lambda Expressions | Notes | Parenthesis in the Parameter List Declaration |
---|---|---|
-> System.out.println() | No parameter syntax, () required | Required |
int a -> a + 1 | Single parameter syntax specifying data type, parenthesis required | Required |
var a -> a + 1 | Single parameter syntax with Local Variable Type Inference, parenthesis required | Required |
(int a, b) -> a + b | Multiple parameter syntax with declared types. All parameters must specify a type | Required |
a, b -> a + b | Multiple parameter syntax with no types. You must use parenthesis | Required |
(a, var b) -> a + b | Multiple parameter syntax with Local Variable Type Inference. You must use var for all parameter if used for one | Required |
(int a, var b) -> a + b | Cannot mix var with actual data types | Required |
Body Declaration Syntax
Now lets look at some of the syntactically correct body declaration syntax for lambda expressions.
Valid Lambda Expressions | Notes |
---|---|
() -> System.out.println(“Max”) | Single line with a statement (not an expression) is valid for methods which have void return type |
() -> isTrue && isAnotherTrue | Single line lambda expression must be an expression which evaluates to the correct return type or covariant, if method has a return type |
(a) -> { return a+2; }; |
Using return requires the use of curly braces for the expression body. Using curly braces requires a return statement if method has a return type. All statements must be terminated with a semi-colon |
(a) -> { if (a % 2) == 0 a = 500; return a+100; }; |
Multiple statements permitted in curly braces. Every statement must terminate with a semi-colon |
(a) -> { int b = 10; if (a % b) == 0 a = 500; return a+100; }; |
You can also declare local variables in the expressions |
And the final table, for incorrect body declaration syntax of lambda expressions.
|Invalid Lambda Expressions|Notes|
|-----------------------:|:----|
|() -> return 1|Using return requires the use of curly braces|
|(a) -> {
if (a % 2) == 0 a = 500,
return a+100;
};|Comma used to separate statements. Must use semi-colon|
So thats all for this section. We will look at code examples for lambda expressions in the next section.