Ender Dincer

Nov 18, 2021

Java Lambda Expressions

Lambda expressions have been introduced in Java 8. A lambda expression is basically a method body without a method name which we can shortly call an anonymous method. It consists of three parts: List of arguments, the arrow operator and the body. Let's see how a simple lambda expression looks like:

Copy

1x -> {
2  return x + 5;
3}

Copy

1Output: -
This expression simply adds 5 to the input parameter and returns it. If the body of the lambda is a single statement, we can simplify it like below:

Copy

1x -> x + 5

Copy

1Output: -
A lambda expression can be assigned to a variable can be reused later.

Copy

1Function<Integer, Integer> function = x -> x + 5;5

Copy

1Output: -
Or it can be passed as a method parameter.

Copy

1public static void myMethod(Function<Integer, Integer> func){
2    //...
3}
4
5public static void main(String[] args){
6    // passing a lambda expression to method
7    myMethod(x -> x + 5);
8}

Copy

1Output: -

What makes this possible and what is "Function<Integer, Integer>" ? To understand lambda expressions in Java we need to understand how they are implemented with functional interfaces.


What is a functional interface?

If an interface has only one abstract method, it is called a functional interface. A functional interface can have default methods (methods with bodies) but can not have multiple abstract methods.


Let's think of a simple example:

Copy

1interface Engine{
2  void start();
3}
4
5public class Car{
6  public string startCar(Engine engine){
7    engine.start();
8    return "Car ready to go!"
9  }
10}

Copy

1Output: -
And say we want to test the same car with three different engines: diesel, petrol, hybrid. We will use three different ways to implement the Engine interface. First, let's start with the simplest one: create a class, implement the Engine interface and override the only abstract method which is start().

Copy

1public class DieselEngine implements Engine{
2
3    @Override
4    public void start() {
5        System.out.println("Diesel engine starting...");
6    }
7}

Copy

1Output: -
Then, of course, we use the diesel engine implementation in the Car constructor. The second way to provide an Engine implementation is by using an anonymous class. It is called anonymous because there is no class name we simply override the abstract methods. This was for the petrol engine.

Copy

1public class Race {
2
3  public static void main(String[] args) {
4    final Car dieselEngineCar = new Car(
5            new DieselEngine()
6    );
7
8    final Car petrolEngineCar = new Car(
9        new Engine() {
10          @Override
11          public void start() {
12            System.out.println("Petrol engine starting...");
13          }
14        }
15    );
16
17    final Car hybridEngineCar = new Car(
18            () -> System.out.println("Hybrid engine starting...")
19    );
20  }
21}

Copy

1Output: -
Final way is to use lambda expressions. The lambda expression does the same with the petrol engine implementation: it overrides the only abstract method in the functional interfaces (the Engine interface). As can be seen above passing a behaviour to a method is much easier and readable thanks to lambda expressions. We could also store the expression in a variable to reuse it like below:

Copy

1final Engine hybridEngine = 
2            () -> System.out.println("Hybrid engine starting..."); 
3
4final Car hybridEngineCar1 = new Car(hybridEngine);
5final Car hybridEngineCar2 = new Car(hybridEngine);

Copy

1Output: -

Predefined functional interfaces and corresponding lambda expressions

In most of the cases you will not have to write your own functional interface if you want use lambda expressions because there are already predefined functional interfaces in java.util.function package. Let's look at the most common ones.

Consumer Interface: Accepts a parameter but has no return type.

Copy

1@FunctionalInterface
2public interface Consumer<T> {
3    void accept(T t);
4    // other default methods...
5}

Copy

1Output: -

Copy

1x -> System.out.println(x)

Copy

1Output: -
Supplier Interface: No input parameters but has a return type.

Copy

1@FunctionalInterface
2public interface Supplier<T> {
3    T get();
4    // other default methods...
5}

Copy

1Output: -

Copy

1() -> Math.random()

Copy

1Output: -
Predicate Interface: Accepts a parameter returns a (primitive) boolean.

Copy

1@FunctionalInterface
2public interface Predicate<T> {
3    boolean test(T t);
4    // other default methods...
5}

Copy

1Output: -

Copy

1x -> (x == 1) ? true : false

Copy

1Output: -
Function Interface: Accepts a parameter and has a return type.

Copy

1@FunctionalInterface
2public interface Function<T, R> {
3    R apply(T t);
4}

Copy

1Output: -

Copy

1x -> x * 5

Copy

1Output: -

These are the main functional interfaces. There are many more, for multiple input parameters, for primitive types etc. Of course you can create your own functional interface and use it as a lambda if you like as long as your interface has only one abstract method!


Lambda expressions and exceptions

Like a method can throw an exception a lambda can too. The only restriction is that the exception (if it's a checked exception) should match the throws clause of the abstract method the lambda overriding.


Happy coding!

Sources


Herbert Schildt, Java, The Complete Reference, Comprehensive Coverage of the Java Language, Ninth Edition

Baeldung, Java 8 Lambda Expressions tips

link

Java™ Platform, Standard Edition 8 API Specification

link

©

enderdincer.com