10.3. Lambda Expressions
Lambda expressions provide a clear and concise way to represent instances of functional interfaces using an expression syntax. They enable functional programming features in Java.
Lambda Expression Syntax
java
(parameters) -> expression
or
java
(parameters) -> { statements; }
Basic Lambda Examples
Simple Lambda Expressions
java
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class BasicLambdaExpressions {
public static void main(String[] args) {
// Supplier - no parameters, returns value
Supplier<String> helloSupplier = () -> "Hello, World!";
System.out.println(helloSupplier.get()); // Output: Hello, World!
// Consumer - one parameter, no return value
Consumer<String> printer = message -> System.out.println(message);
printer.accept("Lambda expressions are powerful!");
// Function - one parameter, returns value
Function<Integer, String> converter = number -> "Number: " + number;
System.out.println(converter.apply(42)); // Output: Number: 42
// Predicate - one parameter, returns boolean
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println("Is 10 even? " + isEven.test(10)); // Output: Is 10 even? true
}
}
Lambda with Multiple Parameters
java
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
public class MultiParamLambda {
public static void main(String[] args) {
// BiFunction - two parameters, returns value
BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b;
System.out.println("Sum: " + adder.apply(5, 3)); // Output: Sum: 8
// BiPredicate - two parameters, returns boolean
BiPredicate<String, String> stringComparator = (s1, s2) -> s1.equals(s2);
System.out.println("Strings equal? " + stringComparator.test("hello", "hello"));
// Complex lambda with multiple statements
BiFunction<Integer, Integer, String> complexOperation = (x, y) -> {
int sum = x + y;
int product = x * y;
return "Sum: " + sum + ", Product: " + product;
};
System.out.println(complexOperation.apply(4, 5)); // Output: Sum: 9, Product: 20
}
}
Lambda Features
Type Inference
java
import java.util.function.Supplier;
import java.util.function.Function;
public class TypeInference {
public static void main(String[] args) {
// Java can infer types from context
Supplier<String> supplier = () -> "Inferred type";
Function<Integer, String> function = num -> "Number: " + num;
// Explicit types (optional)
Supplier<String> explicitSupplier = () -> "Explicit";
Function<Integer, String> explicitFunction = (Integer num) -> "Number: " + num;
System.out.println(supplier.get());
System.out.println(function.apply(100));
}
}
Capturing Variables
java
import java.util.function.Supplier;
import java.util.function.Consumer;
public class VariableCapture {
public static void main(String[] args) {
final String constant = "Constant";
String effectivelyFinal = "Effectively Final";
int counter = 0;
// Can capture final and effectively final variables
Supplier<String> constantSupplier = () -> constant;
Supplier<String> effectivelyFinalSupplier = () -> effectivelyFinal;
// Cannot capture and modify non-final variables
// Consumer<String> invalid = s -> counter++; // Compilation error
System.out.println(constantSupplier.get());
System.out.println(effectivelyFinalSupplier.get());
}
}
Practical Lambda Examples
Custom Functional Interfaces
java
@FunctionalInterface
interface StringProcessor {
String process(String input);
// Can have default methods
default StringProcessor andThen(StringProcessor after) {
return input -> after.process(this.process(input));
}
}
@FunctionalInterface
interface Validator {
boolean validate(String input);
default Validator and(Validator other) {
return input -> this.validate(input) && other.validate(input);
}
}
public class CustomFunctionalInterfaces {
public static void main(String[] args) {
// Using custom functional interface
StringProcessor toUpper = s -> s.toUpperCase();
StringProcessor addExclamation = s -> s + "!";
// Method chaining with default methods
StringProcessor processor = toUpper.andThen(addExclamation);
System.out.println(processor.process("hello")); // Output: HELLO!
// Validator composition
Validator notEmpty = s -> s != null && !s.trim().isEmpty();
Validator minLength = s -> s.length() >= 3;
Validator combinedValidator = notEmpty.and(minLength);
System.out.println("Valid: " + combinedValidator.validate("abc")); // Output: Valid: true
System.out.println("Valid: " + combinedValidator.validate("a")); // Output: Valid: false
}
}
Event Handling with Lambdas
java
import java.util.function.Consumer;
import java.util.ArrayList;
import java.util.List;
class Button {
private String text;
private List<Consumer<Button>> clickListeners = new ArrayList<>();
public Button(String text) {
this.text = text;
}
public void addClickListener(Consumer<Button> listener) {
clickListeners.add(listener);
}
public void click() {
System.out.println("Button '" + text + "' clicked!");
clickListeners.forEach(listener -> listener.accept(this));
}
public String getText() { return text; }
}
public class EventHandlingLambda {
public static void main(String[] args) {
Button saveButton = new Button("Save");
Button cancelButton = new Button("Cancel");
// Add click listeners using lambdas
saveButton.addClickListener(button ->
System.out.println("Saving data for: " + button.getText()));
saveButton.addClickListener(button ->
System.out.println("Logging save action"));
cancelButton.addClickListener(button ->
System.out.println("Cancelling operation"));
// Simulate button clicks
saveButton.click();
cancelButton.click();
}
}
Lambda vs Anonymous Class
Anonymous Class (Pre-Java 8)
java
Supplier<String> anonymousSupplier = new Supplier<String>() {
@Override
public String get() {
return "Hello from anonymous class";
}
};
Lambda Expression (Java 8+)
java
Supplier<String> lambdaSupplier = () -> "Hello from lambda";
Best Practices
- Keep lambdas short and focused: Single responsibility
- Use method references when possible: More readable
- Avoid complex logic: Extract to methods if needed
- Use meaningful parameter names: Better than single letters
java
import java.util.function.Function;
import java.util.function.Predicate;
public class LambdaBestPractices {
public static void main(String[] args) {
// Good - clear and concise
Function<String, Integer> stringLength = s -> s.length();
Predicate<String> isValidEmail = email -> email.contains("@") && email.contains(".");
// Better - with meaningful parameter names
Function<String, Integer> stringLengthGood = inputString -> inputString.length();
Predicate<String> isValidEmailGood = emailAddress ->
emailAddress.contains("@") && emailAddress.contains(".");
// Avoid - too complex, should be a method
Function<String, String> complexFormatter = input -> {
if (input == null) return "Unknown";
String trimmed = input.trim();
if (trimmed.isEmpty()) return "Empty";
return trimmed.substring(0, 1).toUpperCase() +
trimmed.substring(1).toLowerCase();
};
// Better - extract complex logic to method
Function<String, String> betterFormatter = LambdaBestPractices::formatString;
}
private static String formatString(String input) {
if (input == null) return "Unknown";
String trimmed = input.trim();
if (trimmed.isEmpty()) return "Empty";
return trimmed.substring(0, 1).toUpperCase() +
trimmed.substring(1).toLowerCase();
}
}