Java lambda expression can be considered as one of the coolest feature introduced in Java 8. It is Java’s first step into functional programming. A Java lambda expression can be considered as a function which can be created without belonging to any class. Also, it can be passed around as if it was an object and executed on demand.
Java Lambda Expression
Basically, Lambda Expression is a concise representation of an anonymous function that can be passed around. So, it has the following properties:
- Anonymous: We can say anonymous because it doesn’t have an explicit name like a method would normally have. So, it’s definitely less to write and think about.
- Function: It can be thought of as a function because a lambda isn’t associated with a particular class like a method is. But like a method, a lambda has a list of parameters, a body, a return type, and a possible list of exceptions that can be thrown.
- Passed around: A lambda expression can be passed as argument to a method or stored in a variable.
- Concise: We don’t need to write a lot of boilerplate like we do for anonymous classes.
Technically, Lambda expressions do not allow us to do anything that we did not do prior to Java 8. It’s just that we don’t need to write clumsy code in order to use the behaviour parameterisation.
Java Lambda Expression Example
An example would certainly help to get a clear idea. Consider Apple
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Apple { String color; Double weight; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Double getWeight() { return weight; } public void setWeight(Double weight) { this.weight = weight; } } |
If we want to write a custom comparator to compare the Apples by weight, until Java 7 we used to write it in the following way:
1 2 3 4 5 6 7 |
Comparator<Apple> byWeight = new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } }; |
Now, using Lambda Expression, we can write the same thing as:
1 2 3 4 |
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); |
Java Lambda Expression Explanation
Let’s dig into the individual pieces in this expression:
- A list of parameters: In this case it mirrors the parameters of the compare method of a
Comparator – two Apple objects. - An arrow: The arrow -> separates the list of parameters from the body of the lambda.
- The body of the lambda: Compare two Apples using their weights. The expression is considered
the lambda’s return value.
Java 8 Lambda Expression usage
Let’s look at some use case examples of java lambda expression.
- A boolean expression:
(List list) -> list.isEmpty()
- Creating objects:
() -> new Apple()
- Consuming from an object:
(Apple a) -> { System.out.println(a.getWeight()); }
- Select/extract from an object:
(String s) -> s.length()
- Produce a single value by performing computation on two values:
(int a, int b) -> a * b
- Compare two objects:
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
Java Functional Interfaces
It is obvious to wonder where such expressions are allowed? Previously we saw a lambda expression to replace the Comparator implementation. we can also use it as follows:
1 2 3 4 5 |
List<String> greenApples = listOfApples.stream().filter(a -> a.getColor().equals("Green")) .collect(Collectors.toList()); |
Here, filter()
method expects a Predicate. We just passed a lambda instead of the required interface.
Basically, we can use them in the context of Functional Interfaces. Now, what exactly are Functional Interfaces?
In a nutshell, a Functional Interface is an interface that specifies exactly one abstract method.
Some known Functional Interfaces in Java are Comparator
, Runnable
, ActionListener
, Callable, etc.
Also note that an interface is still a functional interface even if it defines one or more default methods, as long as it defines a single abstract method.
And of course, we can always define custom functional interfaces apart from the default ones provided by Java.
Java Lambda expressions lets us provide the implementation for the abstract method of the functional interface directly, inline and the whole expression is treated as an instance of a concrete implementation of that interface.
Java Lambda Expressions vs Anonymous class
Considering our previous examples, the same things can be achieved using the anonymous class as well, but the code will be clumsier if we are writing the implementation inline.
Let’s see an example of Runnable
. Consider a process
method that takes in a Runnable
instance:
1 2 3 4 5 6 7 |
public class RunnableDemo { public static void process(Runnable r){ r.run(); } } |
Without using Lambda, we can implement it as:
1 2 3 4 5 6 7 8 9 |
Runnable runnableWithAnonymousClass = new Runnable() { @Override public void run() { System.out.println("I am inside anonymous class implementation."); } }; process(runnableWithAnonymousClass); |
Using the lambda expression:
1 2 3 4 |
Runnable runnableWithLambda = () -> System.out.println("I am inside Lmabda Expression."); process(runnableWithLambda); |
Also, in cases where we are using this Runnable implementation only once, we can also provide it directly while calling the process method:
1 2 3 |
process(() -> System.out.println("I am inside Lambda Expression.")); |
Function Descriptor
An important thing to understand while working with lambda expression is a functional descriptor. The signature of the abstract method of the functional interface essentially describes the signature of the lambda expression. We call this abstract method a function descriptor.
For example, the Runnable interface can be viewed as the signature of a function that accepts nothing and returns nothing (void) because it has only one abstract method called run
, which accepts nothing and returns nothing (void).
That’s all for the Java Lambda Expressions.
Reference: API Doc