Lambda Expressions

By the Syncfusion DevTutorials team · Last updated

Write short, nameless functions with lambdas, use them with functional interfaces like Predicate, Function, Consumer and Supplier, and shorten them with method references.

Think about leaving a note for a friend who is minding your shop.

You could write a whole letter: "Dear Ravi, I hope you are well. When a customer comes in, please check whether they are a member. If they are, give them ten percent off. Thank you, Asha."

Or you could stick a short note on the till: "Member → 10% off."

Both say the same thing. The short note is quicker to write and quicker to read.

A lambda expression is that short note. It lets you hand a small piece of behaviour to a method, in one line, without writing a whole class.

In simple words: a lambda is a short, nameless method that you can pass around like a value.

What You Will Learn

In this lesson, you will learn:

  1. What lambda expressions are, and why Java added them.
  2. How lambdas and functional interfaces work together.
  3. The lambda syntax, with and without parameters.
  4. How lambdas return values.
  5. The four most used built-in functional interfaces: Predicate, Function, Consumer and Supplier.
  6. How method references make lambdas even shorter.
  7. How lambdas compare with anonymous classes.

Start With a Working Example

Press Run.

One line of behaviour, passed into a method

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> marks = new ArrayList<>(List.of(72, 35, 88, 28, 64));

        marks.removeIf(m -> m < 40);
        System.out.println("Passing marks: " + marks);

        marks.forEach(m -> System.out.println("Mark: " + m));

        marks.sort((a, b) -> b - a);
        System.out.println("Highest first: " + marks);
    }
}

Expected output:

Passing marks: [72, 88, 64]
Mark: 72
Mark: 88
Mark: 64
Highest first: [88, 72, 64]

Each -> is a lambda. m -> m < 40 is a rule that says which marks to remove. (a, b) -> b - a is a rule that says how to compare two marks.

The methods removeIf, forEach and sort do the looping. The lambdas only say what to do with each item.

Try This

Change m < 40 to m < 70. Only one line changes, and now only 72 and 88 remain.

Introduction to Lambda Expressions

What are Lambda Expressions?

A lambda expression is a short block of code that takes parameters and produces a result, but has no name and does not belong to any class.

(parameters) -> body
  • On the left of the arrow are the parameters, the values it receives.
  • On the right is the body, the work it does.
  • The arrow -> separates the two. Read it as "goes to" or "becomes".

So x -> x * 2 reads as "x becomes x times two".

You can store a lambda in a variable, pass it to a method, or return it from a method. That is what makes it powerful: behaviour becomes a value.

Why Lambda Expressions Were Introduced

Before Java 8 (2014), passing behaviour to a method meant writing an anonymous class. Even a one-line rule needed five or six lines of setup code.

Lambdas were added in Java 8 to:

  • cut repeated code, so a simple rule takes one line;
  • make code easier to read, because the important part is not buried;
  • support streams, a powerful way to process collections, which you will learn in the next lesson;
  • support parallel work, because behaviour can be handed to many threads.

The same rule, the old way and the lambda way

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Meena", "Ravi", "Arjun", "Asha"));

        names.sort(new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        });
        System.out.println("Old way: " + names);

        names.sort((a, b) -> b.length() - a.length());
        System.out.println("Lambda:  " + names);
    }
}

Expected output:

Old way: [Ravi, Asha, Meena, Arjun]
Lambda:  [Meena, Arjun, Ravi, Asha]

The old way needed six lines to say "compare by length". The lambda says it in one.

Functional Interfaces

A lambda cannot float around on its own. It always fills in a functional interface.

A functional interface is an interface with exactly one abstract method. You met them in the Interfaces lesson.

The lambda becomes the body of that one method. Java can tell which method it is, because there is only one.

@FunctionalInterface
interface Greeter {
    String greet(String name);   // the one abstract method
}

Greeter hello = name -> "Hello, " + name;   // the lambda is greet's body

The type of the variable, Greeter, tells Java what the lambda must look like: one String in, one String out.

@FunctionalInterface is optional. It asks the compiler to check there is exactly one abstract method. If someone adds a second one later, the code stops compiling, which protects every lambda that uses it.

Your own functional interface, filled in by lambdas

public class Main {
    public static void main(String[] args) {
        Discount festival = price -> price * 80 / 100;
        Discount flatFifty = price -> price - 50;
        Discount none = price -> price;

        int price = 1000;
        System.out.println("Festival: " + festival.apply(price));
        System.out.println("Flat 50 off: " + flatFifty.apply(price));
        System.out.println("No discount: " + none.apply(price));
    }
}

@FunctionalInterface
interface Discount {
    int apply(int price);
}

Expected output:

Festival: 800
Flat 50 off: 950
No discount: 1000

Each lambda is a different Discount. Calling apply runs that lambda's body.

A functional interface can still have default and static methods. Only the abstract methods are counted, and there must be exactly one.

Lambda Syntax

A lambda has a few forms. Java lets you leave out parts when they are obvious.

FormExampleWhen to use it
No parameters() -> "Hello"The method takes nothing
One parametername -> name.length()Brackets are optional with exactly one parameter
Several parameters(a, b) -> a + bBrackets are required
Typed parameters(int a, int b) -> a + bTypes are usually left out, because Java works them out
Block bodyx -> { int y = x * 2; return y + 1; }More than one statement

Rules to remember:

  • With one parameter and no type, you can leave out the brackets: x -> x * 2.
  • With zero or several parameters, the brackets are required: () -> 5, (a, b) -> a * b.
  • A one-line body needs no braces and no return.
  • A body in braces { } needs a return if it gives back a value, and ; after each statement.

Lambda with Parameters

A lambda's parameters work like a method's parameters. Their types come from the functional interface.

Zero, one and two parameters

public class Main {
    public static void main(String[] args) {
        Greeting welcome = () -> System.out.println("Welcome to the store!");
        welcome.say();

        Shouter loud = text -> System.out.println(text.toUpperCase() + "!");
        loud.shout("big sale today");

        Adder add = (a, b) -> a + b;
        System.out.println("12 + 30 = " + add.sum(12, 30));
    }
}

interface Greeting {
    void say();
}

interface Shouter {
    void shout(String text);
}

interface Adder {
    int sum(int a, int b);
}

Expected output:

Welcome to the store!
BIG SALE TODAY!
12 + 30 = 42

welcome has no parameters, so it starts with empty brackets (). loud has one, so it needs no brackets. add has two, so they are in brackets.

Java worked out that text is a String, and a and b are int, from the interfaces.

Using Variables From Outside

A lambda can use variables from the code around it, but only if they never change after being set. Java calls these effectively final.

int bonus = 5;
Adder withBonus = (a, b) -> a + b + bonus;   // fine
bonus = 10;                                   // now the lambda line will not compile

Lambda parameters cannot reuse the name of a local variable that is already in scope. If name is already a variable in the method, name -> name.length() does not compile. Pick a different parameter name, like n.

Lambda with Return Values

When a lambda's body is a single expression, its value is returned automatically. You do not write return.

When the body needs several statements, put them in braces and use return.

Expression body vs block body

public class Main {
    public static void main(String[] args) {
        Calculator square = n -> n * n;

        Calculator tax = amount -> {
            int rate = amount > 1000 ? 18 : 5;
            int taxDue = amount * rate / 100;
            return amount + taxDue;
        };

        Checker isAdult = age -> age >= 18;

        System.out.println("Square of 9: " + square.calc(9));
        System.out.println("Price with tax (500): " + tax.calc(500));
        System.out.println("Price with tax (2000): " + tax.calc(2000));
        System.out.println("Is 16 an adult? " + isAdult.check(16));
    }
}

interface Calculator {
    int calc(int value);
}

interface Checker {
    boolean check(int value);
}

Expected output:

Square of 9: 81
Price with tax (500): 525
Price with tax (2000): 2360
Is 16 an adult? false

n -> n * n returns n * n automatically. The tax lambda has three statements, so it uses braces and an explicit return.

age -> age >= 18 returns a boolean, because a comparison is true or false.

Built-in Functional Interfaces

You do not need to write your own interface for every lambda. Java provides many ready-made ones in java.util.function. Four of them cover most needs.

InterfaceMethodTakesReturnsThink of it as
Predicate<T>test(T)One valuebooleanA yes/no question
Function<T, R>apply(T)One valueA valueA converter
Consumer<T>accept(T)One valueNothingA doer
Supplier<T>get()NothingA valueA maker

Predicate

A Predicate<T> takes a value and answers true or false. Use it for checks and filters.

Predicates can be combined with and, or and negate.

Function

A Function<T, R> takes a value of type T and returns a value of type R. Use it to convert one thing into another.

Functions can be chained with andThen.

Consumer

A Consumer<T> takes a value and returns nothing. It just does something with it, like printing or saving.

Supplier

A Supplier<T> takes nothing and returns a value. Use it to create or fetch something on demand.

Predicate, Function, Consumer and Supplier

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Predicate<Integer> isEven = n -> n % 2 == 0;
        Predicate<Integer> isBig = n -> n > 100;
        System.out.println("Is 42 even? " + isEven.test(42));
        System.out.println("Is 42 even AND big? " + isEven.and(isBig).test(42));
        System.out.println("Is 7 NOT even? " + isEven.negate().test(7));

        Function<String, Integer> length = text -> text.length();
        Function<Integer, Integer> doubled = n -> n * 2;
        System.out.println("Length of 'Chennai': " + length.apply("Chennai"));
        System.out.println("Length, then doubled: " + length.andThen(doubled).apply("Chennai"));

        Consumer<String> welcome = name -> System.out.println("Welcome, " + name + "!");
        welcome.accept("Asha");

        Supplier<String> defaultCity = () -> "Bengaluru";
        System.out.println("Default city: " + defaultCity.get());
    }
}

Expected output:

Is 42 even? true
Is 42 even AND big? false
Is 7 NOT even? true
Length of 'Chennai': 7
Length, then doubled: 14
Welcome, Asha!
Default city: Bengaluru

isEven.and(isBig) builds a new predicate from two others. length.andThen(doubled) runs length first, then feeds its answer to doubled.

Two-Argument Versions

Some interfaces have a version that takes two inputs:

InterfaceTakesReturnsExample
BiFunction<T, U, R>Two valuesA value(price, qty) -> price * qty
BiPredicate<T, U>Two valuesboolean(a, b) -> a.equals(b)
BiConsumer<T, U>Two valuesNothing(key, value) -> System.out.println(key + "=" + value)
UnaryOperator<T>One valueSame types -> s.trim()
BinaryOperator<T>Two values of the same typeSame type(a, b) -> a + b

Pass a Predicate into your own method

import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        List<Integer> prices = List.of(120, 850, 45, 1500, 300);

        System.out.println("Under 200: " + count(prices, p -> p < 200));
        System.out.println("Over 500: " + count(prices, p -> p > 500));
        System.out.println("Even: " + count(prices, p -> p % 2 == 0));

        BiFunction<Integer, Integer, Integer> lineTotal = (price, qty) -> price * qty;
        System.out.println("3 items at 45: " + lineTotal.apply(45, 3));
    }

    static int count(List<Integer> values, Predicate<Integer> rule) {
        int total = 0;
        for (int v : values) {
            if (rule.test(v)) {
                total++;
            }
        }
        return total;
    }
}

Expected output:

Under 200: 2
Over 500: 2
Even: 4
3 items at 45: 135

count is written once. Each call passes a different rule as a lambda, so the same method answers three different questions.

Easy Way to Remember: Predicate asks a question. Function changes a value. Consumer uses a value. Supplier makes a value.

Method References

A method reference is an even shorter way to write a lambda that only calls one existing method.

It uses a double colon ::.

name -> System.out.println(name)   // lambda
System.out::println                 // method reference

Both do exactly the same thing. The method reference just names the method instead of calling it.

There are four kinds:

KindMethod referenceSame as the lambda
Static methodInteger::parseInts -> Integer.parseInt(s)
Method of a particular objectSystem.out::printlnx -> System.out.println(x)
Method of the parameter itselfString::toUpperCases -> s.toUpperCase()
ConstructorArrayList::new() -> new ArrayList<>()

Method references replace one-call lambdas

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("ravi", "asha", "meena"));

        names.replaceAll(String::toUpperCase);
        names.forEach(System.out::println);

        Function<String, Integer> toNumber = Integer::parseInt;
        System.out.println("Parsed + 1: " + (toNumber.apply("41") + 1));

        Function<String, Integer> length = String::length;
        System.out.println("Length of MEENA: " + length.apply("MEENA"));

        Supplier<List<String>> newList = ArrayList::new;
        List<String> cart = newList.get();
        cart.add("Pen");
        System.out.println("New cart: " + cart);
    }
}

Expected output:

RAVI
ASHA
MEENA
Parsed + 1: 42
Length of MEENA: 5
New cart: [Pen]

String::toUpperCase turned every name into capitals. System.out::println printed each one.

Use a method reference when the lambda only passes its parameter straight into one method. If the lambda does anything more, like s -> s.toUpperCase() + "!", keep the lambda.

Lambda vs Anonymous Classes

Both can implement an interface on the spot. They are not exactly the same.

this means different things in each

public class Main {
    String name = "Main object";

    public static void main(String[] args) {
        new Main().compare();
    }

    void compare() {
        Runnable anonymous = new Runnable() {
            String name = "Anonymous object";

            @Override
            public void run() {
                System.out.println("Anonymous class: this.name = " + this.name);
            }
        };

        Runnable lambda = () -> System.out.println("Lambda: this.name = " + this.name);

        anonymous.run();
        lambda.run();
    }
}

Expected output:

Anonymous class: this.name = Anonymous object
Lambda: this.name = Main object

Inside the anonymous class, this means the anonymous object itself. Inside the lambda, this means the object of the surrounding code, here the Main object. A lambda does not create a new object scope.

Runnable is a built-in functional interface whose one method, run(), takes nothing and returns nothing.

Lambda expressionAnonymous class
LengthUsually one lineSeveral lines
Works withFunctional interfaces only (one abstract method)Any interface or class, any number of methods
Can have its own fields?NoYes
What this meansThe surrounding objectThe anonymous object itself
Can extend a class?NoYes
Uses outer local variablesOnly effectively final onesOnly effectively final ones
Use it whenYou need a short piece of behaviourYou need several methods, fields, or to extend a class

Common mistakes

The following mistakes are common when writing lambda expressions and method references. Each row shows what you see, why it happens and how to fix it.

SymptomCauseFix
Syntax error at a, b ->Several parameters without bracketsWrite (a, b) ->
missing return statement in a lambdaA block body gives a value with no returnAdd return, or remove the braces
must be final or effectively finalA captured variable was changedDo not change variables a lambda uses
is not a functional interfaceThe interface has more than one abstract methodUse a single-method interface
method println in class PrintStream cannot be appliedBrackets were used in a method referenceWrite System.out::println
A lambda becomes hard to readToo much logic inside itMove it into a named method

What to Remember

  • A lambda is a short, nameless method: (parameters) -> body. It turns behaviour into a value.
  • Lambdas were added in Java 8 to cut repeated code and to power streams.
  • A lambda always fills in a functional interface, which has exactly one abstract method.
  • One parameter needs no brackets. A one-line body needs no braces and no return.
  • Predicate tests, Function converts, Consumer uses and Supplier creates.
  • Method references, like String::length and System.out::println, are shortcuts for one-call lambdas.
  • Lambdas are shorter than anonymous classes, but they only work with functional interfaces, and this means the surrounding object.

Next: Streams