A Java lambda expression supplies the implementation of a functional interface without declaring a separate implementation class. Its shape is parameters, an arrow, and a body. You will often meet lambdas when sorting collections, filtering values, or registering a task. The important question is which interface the surrounding code expects: that target determines the parameter types and result. These examples use Java 17 and concentrate on small decisions you can read directly.
Understand the target type
Predicate<Integer> represents a test of an integer. Its abstract method is test, which accepts one value and returns a boolean. Assigning count -> count >= 6 to that interface gives test its implementation. The lambda is not executed during assignment; it runs when test is called. Java checks that the expression returns the required type. A lambda returning a String cannot be assigned to Predicate<Integer>, even if its parameter looks correct.
Expression bodies and block bodies
An expression body evaluates one expression. For a Function<String, Integer>, text -> text.length() produces the length. A block body uses braces and can declare intermediate values or make decisions. If the functional method returns a value, every applicable path in the block must return one. Consumer<String> represents an action with no result, so printing is valid there without returning a value. The interface contract determines which form is appropriate.
Parameters and inferred types
Java can usually infer parameter types from the assignment or method argument. Write (left, right) -> left.length() - right.length() for a simple comparison, or provide explicit types for clarity. A single untyped parameter can omit parentheses; zero parameters and multiple parameters need them. Do not mix an explicitly typed parameter with an untyped one. Using var for lambda parameters is possible since Java 11, but it adds little to these introductory examples.
Capturing local variables
A lambda can read a local variable from its enclosing method when that variable is final or effectively final. Effectively final means you do not assign a new value after initialization. In the first program, minimum remains six, so the predicate can capture it. Trying to increment minimum later causes a compilation error. An object reached through a captured reference can still be mutable, so this restriction does not make all surrounding state immutable.
A lambda compared with an anonymous class
An anonymous class implements the same interface with a named method body. It is useful to write one once because it makes the contract visible: the lambda replaces the implementation of test, not the interface itself. Inside a lambda, this refers to the enclosing object. Inside an anonymous class, this refers to that anonymous object. This distinction matters when moving instance-method code between the two forms, although our static main examples do not need this.
When a named method is clearer
A short predicate such as score -> score >= 50 is easy to understand beside the operation that uses it. A lambda with several nested conditions, database access, and error recovery hides too much work. Extract that behavior to a named method and consider a method reference. Avoid modifying a collection while a stream reads it, and avoid depending on side effects to explain a calculation. Compact syntax should preserve the purpose of the operation.
Practice tasks and checks
Extend TicketPredicate with a second rule requiring no more than twelve tickets. Combine the lower and upper checks with and, and predict the outcomes for five, six, twelve, and thirteen. These four inputs test both sides of each boundary. Then replace the lambda assigned to groupDiscount with an anonymous Predicate implementation that overrides test. The printed results should remain identical. Finally, test the late-fee function with zero, five, six, and seven days. Confirm that the six-day cap applies to later returns and that negative days are rejected. Keep the input type and return type visible in the Function declaration. This exercise checks the interface contract, boolean conditions, and the exact moment the body executes rather than only your ability to copy arrow syntax.
Java Summary
- A lambda needs a compatible functional-interface target.
- Captured local variables must be final or effectively final.
- Expression and block bodies must satisfy the target return contract.
- Continue with functional interfaces when these examples are clear.
Java Examples
Each example shows the code, the output it produces and what each part does.
Testing a captured threshold
Save this program as TicketPredicate.java. Compile it with javac TicketPredicate.java and run it with java TicketPredicate.
Expected output:
The variable minimum is initialized before the predicate is created and is never reassigned. Calling test with eight evaluates eight greater than or equal to six, producing true. Calling the same predicate with four produces false. The two calls share the same rule but evaluate different arguments. There is no loop and no stored list of results: each call computes its boolean at that moment.
import java.util.function.Predicate;
public class TicketPredicate {
public static void main(String[] args) {
int minimum = 6;
Predicate<Integer> groupDiscount = tickets -> tickets >= minimum;
System.out.println(groupDiscount.test(8));
System.out.println(groupDiscount.test(4));
}
}true
falseReturn a capped late fee
Save this program as LambdaLateCharge.java. Compile it with javac LambdaLateCharge.java and run it with java LambdaLateCharge.
Expected output:
Function takes an input and produces a result, so apply executes this lambda. Three late days are below the six-day billing cap and produce a charge of six units. Nine late days are capped at six billable days and produce twelve units. Naming billableDays makes the limit visible, while validation rejects a negative day count. Capping before multiplying also avoids multiplying an arbitrarily large positive day count before applying the limit.
import java.util.function.Function;
public class LambdaLateCharge {
public static void main(String[] args) {
Function<Integer, Integer> lateCharge = days -> {
if (days < 0) throw new IllegalArgumentException("Negative days");
int billableDays = Math.min(days, 6);
return billableDays * 2;
};
System.out.println(lateCharge.apply(3));
System.out.println(lateCharge.apply(9));
}
}6
12Using a lambda as a method argument
Save this program as LabelOrder.java. Compile it with javac LabelOrder.java and run it with java LabelOrder.
Expected output:
List.sort expects a Comparator, whose comparison result is negative, zero, or positive. Integer.compare returns that result for the two lengths. Sorting changes the ArrayList itself, placing the shortest label first. List.of alone would create an unmodifiable list, so the example copies it into an ArrayList before sorting. Equal lengths compare as zero; a secondary comparison would be needed if you also wanted alphabetical ordering among ties.
import java.util.ArrayList;
import java.util.List;
public class LabelOrder {
public static void main(String[] args) {
List<String> labels = new ArrayList<>(List.of("Lighthouse", "Bay", "Harbor"));
labels.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println(labels);
}
}[Bay, Harbor, Lighthouse]Java FAQ
Can I assign a lambda to Object?
Not directly. Object does not identify a functional method, so it provides no target contract for the lambda. Assign to a functional interface such as Runnable or Predicate first; that reference can then also be treated as an Object when required.
Does creating a lambda immediately run its body?
No. The body runs when the functional method is invoked. Creating a Predicate stores a behavior; calling test evaluates it. This distinction explains why a captured threshold is available when later calls supply their own arguments.
Can a lambda throw a checked exception?
Only if the functional interface's method declares it. Standard interfaces such as Function and Predicate don't, so a lambda that calls Files.readString won't compile there. Handle the exception inside the lambda, wrap it in an unchecked exception, or define your own functional interface whose method declares throws IOException.
Can a lambda have no parameters?
Yes. Write empty parentheses: () -> System.out.println("Done") implements Runnable, and () -> LocalDate.now() implements Supplier<LocalDate>. With exactly one parameter you may omit the parentheses, as in x -> x * 2.
Is a lambda an object?
Yes. At run time a lambda is an instance of its functional interface, so it can be stored in variables, passed to methods and kept in collections. Don't rely on its identity, toString or class name, though: the JVM decides how lambda objects are created.
Java Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which change prevents the threshold predicate from compiling?
What does lateCharge.apply(6) return?
Java Quiz
2 questions to check what you learned.
Which interface is suitable for an operation that prints a String and returns nothing?
- Consumer<String>
- Predicate<String>
- Function<String, Boolean>
Why is the labels list copied into an ArrayList?
- List.sort needs a modifiable list
- Comparators require arrays
- List.of cannot contain strings