A lambda expression is an anonymous function that can be treated as a value - it can be assigned to variables, passed as arguments, or returned from methods. Lambda expressions provide a concise way to represent functional interfaces (interfaces with a single abstract method).
A lambda expression can be used in several contexts:
-
Initializing a variable of a functional interface type.
-
As method or constructor arguments where a functional interface is expected.
-
As method return values.
-
In conditional expressions "?:" (ternary operator).
The syntax of a lambda expression is as follows:
parameters -> body
The arrow operator
-> separates the parameter list from the body of the lambda expression.
-
Parameters: Define the input variables for the lambda expression.
Parameter types can be omitted if the compiler can infer them from the context (target typing). When types are specified, they must be specified for all parameters or none.
Parentheses around parameters are:
- Required for zero parameters:
()
- Optional for exactly one parameter:
x
or (x)
- Required for multiple parameters:
(x, y)
Examples:
- explicit types:
(Integer i, Integer j) -> i + j
- inferred types:
(i, j) -> i + j
- no parameters:
() -> Math.random()
- single parameter:
i -> i * 2
-
Body: Can be either a single expression or a block of statements.
For single expressions, the return
keyword is implicit. For statement blocks, you must explicitly use return
if the lambda should return a value.
Examples:
- expression body:
(String s) -> s.toUpperCase()
- statement block:
(String s) -> { return s.toUpperCase(); }
- void lambda:
() -> System.out.println("Hello!")
-
Return Type: The return type is inferred by the compiler based on the lambda body and target type.
Examples:
- returning String:
() -> "hello!"
- with explicit return:
() -> { return "hello!"; }
- void return:
() -> System.out.println("Hello!")
- higher-order function:
x -> y -> x + y
(returns a function)
Lambda expressions have some limitations compared to regular methods:
- Cannot use
break
or continue
statements unless inside a loop within the lambda.
- Cannot throw checked exceptions unless the functional interface's method declares them.
- Must be compatible with the target functional interface's method signature.
import java.util.function.Predicate;
public class TestLambda {
public Predicate<Integer> testCheckedException() {
Predicate<Integer> predicate = t -> {
if (t == 0) {
// Runtime exceptions are allowed
throw new IllegalStateException("t should not be equal to 0");
}
// Checked exceptions are NOT allowed unless declared in the functional interface
if (t % 2 == 0) {
throw new Exception("t should be odd"); // Compiler error: Unhandled exception type Exception
}
return t >= 1;
};
return predicate;
}
}
Lambda expressions can access variables from their enclosing scope:
- Local variables: Must be effectively final (not reassigned after initialization).
- Instance variables: Can be accessed and modified freely.
- Static variables: Can be accessed and modified freely.
import java.util.function.Predicate;
public class TestLambda {
private Integer instanceVar = 1;
private static Integer classVar = 1;
public Predicate<Integer> testVariableCapture() {
final Integer finalVar = 1;
Integer effectivelyFinalVar = 1; // must not be modified after this point
Predicate<Integer> predicate = t -> {
// Can modify instance and static variables
instanceVar = t;
classVar = t * 2;
// Can read final and effectively final local variables
return t > finalVar && t < effectivelyFinalVar;
};
// effectivelyFinalVar = 1; // Un-commenting this line will cause a compile error: Local variable effectivelyFinalVar defined in an enclosing scope must be final or effectively final
return predicate;
}
}
Functional Interfaces:
Lambda expressions can only be used where a functional interface is expected. A functional interface has exactly one abstract method.
Examples:
Runnable
: () -> void
Supplier<T>
: () -> T
Consumer<T>
: T -> void
Function<T,R>
: T -> R
Predicate<T>
: T -> boolean
Runnable Example:
Runnable task = () -> System.out.println("Task is running...");
task.run(); // Executes the lambda