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:
- What lambda expressions are, and why Java added them.
- How lambdas and functional interfaces work together.
- The lambda syntax, with and without parameters.
- How lambdas return values.
- The four most used built-in functional interfaces:
Predicate,Function,ConsumerandSupplier. - How method references make lambdas even shorter.
- 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.
| Form | Example | When to use it |
|---|---|---|
| No parameters | () -> "Hello" | The method takes nothing |
| One parameter | name -> name.length() | Brackets are optional with exactly one parameter |
| Several parameters | (a, b) -> a + b | Brackets are required |
| Typed parameters | (int a, int b) -> a + b | Types are usually left out, because Java works them out |
| Block body | x -> { 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 areturnif 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.
| Interface | Method | Takes | Returns | Think of it as |
|---|---|---|---|---|
Predicate<T> | test(T) | One value | boolean | A yes/no question |
Function<T, R> | apply(T) | One value | A value | A converter |
Consumer<T> | accept(T) | One value | Nothing | A doer |
Supplier<T> | get() | Nothing | A value | A 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:
| Interface | Takes | Returns | Example |
|---|---|---|---|
BiFunction<T, U, R> | Two values | A value | (price, qty) -> price * qty |
BiPredicate<T, U> | Two values | boolean | (a, b) -> a.equals(b) |
BiConsumer<T, U> | Two values | Nothing | (key, value) -> System.out.println(key + "=" + value) |
UnaryOperator<T> | One value | Same type | s -> s.trim() |
BinaryOperator<T> | Two values of the same type | Same 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:
| Kind | Method reference | Same as the lambda |
|---|---|---|
| Static method | Integer::parseInt | s -> Integer.parseInt(s) |
| Method of a particular object | System.out::println | x -> System.out.println(x) |
| Method of the parameter itself | String::toUpperCase | s -> s.toUpperCase() |
| Constructor | ArrayList::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 expression | Anonymous class | |
|---|---|---|
| Length | Usually one line | Several lines |
| Works with | Functional interfaces only (one abstract method) | Any interface or class, any number of methods |
| Can have its own fields? | No | Yes |
What this means | The surrounding object | The anonymous object itself |
| Can extend a class? | No | Yes |
| Uses outer local variables | Only effectively final ones | Only effectively final ones |
| Use it when | You need a short piece of behaviour | You 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.
| Symptom | Cause | Fix |
|---|---|---|
Syntax error at a, b -> | Several parameters without brackets | Write (a, b) -> |
missing return statement in a lambda | A block body gives a value with no return | Add return, or remove the braces |
must be final or effectively final | A captured variable was changed | Do not change variables a lambda uses |
is not a functional interface | The interface has more than one abstract method | Use a single-method interface |
method println in class PrintStream cannot be applied | Brackets were used in a method reference | Write System.out::println |
| A lambda becomes hard to read | Too much logic inside it | Move 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. Predicatetests,Functionconverts,Consumeruses andSuppliercreates.- Method references, like
String::lengthandSystem.out::println, are shortcuts for one-call lambdas. - Lambdas are shorter than anonymous classes, but they only work with functional interfaces, and
thismeans the surrounding object.
Next: Streams