foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • 5
  • quiz
Java
  • Master the different lambda syntax variations
  • Understand when to use expression vs block body
  • Learn parameter type inference rules
  • Apply best practices for readable lambdas

Lambda Syntax and Variations

You've seen basic lambda syntax. Time to explore all the variations—from verbose to ultra-concise, single-line to multi-statement. Knowing which form to use makes your code clearer.

Full Lambda Syntax

The general form of a lambda expression:

(parameters) -> expression
// or
(parameters) -> { statements; }

The three parts:

  1. Parameter list in parentheses
  2. Arrow token ->
  3. Body - either a single expression or a block of statements

Parameter Variations

No Parameters

Use empty parentheses:

Runnable r = () -> System.out.println("Hello!");
Supplier<Double> random = () -> Math.random();

One Parameter

Parentheses are optional for a single parameter:

// With parentheses
Consumer<String> c1 = (s) -> System.out.println(s);

// Without parentheses (preferred for single param)
Consumer<String> c2 = s -> System.out.println(s);

Multiple Parameters

Parentheses are required:

BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Comparator<String> comp = (s1, s2) -> s1.compareTo(s2);

With Explicit Types

Usually types are inferred, but you can specify them:

// Type inference (preferred)
BiFunction<String, String, Integer> compare = (a, b) -> a.compareTo(b);

// Explicit types
BiFunction<String, String, Integer> compare2 = (String a, String b) -> a.compareTo(b);

Rule: If you specify types for one parameter, you must specify them for all.

// INVALID: Can't mix typed and untyped
// (String a, b) -> a.compareTo(b);  // Compilation error!

Body Variations

Expression Body

For simple operations, use a single expression (no braces, no return):

// The expression's value is automatically returned
Function<Integer, Integer> square = x -> x * x;
Predicate<String> isEmpty = s -> s.isEmpty();
BiFunction<Integer, Integer, Integer> max = (a, b) -> Math.max(a, b);

Block Body

For multiple statements, use braces and explicit return (if needed):

Function<Integer, Integer> factorial = n -> {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;  // Explicit return required
};

Consumer<String> logger = message -> {
    String timestamp = LocalDateTime.now().toString();
    System.out.println(timestamp + ": " + message);
    // No return needed for void methods
};

Return Statement Rules

Expression Body

  • No return keyword
  • The expression value is implicitly returned
// Correct: implicit return
Function<Integer, Integer> double_ = x -> x * 2;

// WRONG: can't use return with expression body
// Function<Integer, Integer> wrong = x -> return x * 2;  // Error!

Block Body

  • Must use return for non-void methods
  • No return for void methods
// Correct: explicit return in block
Function<Integer, Integer> triple = x -> {
    return x * 3;
};

// Correct: no return for void
Consumer<String> printer = s -> {
    System.out.println(s);
};

Type Inference

Java infers lambda parameter types from context:

// Type inferred from Comparator<String>
Comparator<String> comp = (a, b) -> a.length() - b.length();

// Type inferred from List<String>.forEach(Consumer<String>)
List<String> names = Arrays.asList("Alice", "Bob");
names.forEach(name -> System.out.println(name));  // name is String

// Type inferred from method parameter
public void process(Predicate<Integer> p) { ... }
process(n -> n > 0);  // n is Integer

Syntax Comparison Table

Scenario Lambda Syntax
No params, expression () -> expression
No params, block () -> { statements; }
One param, expression x -> expression
One param, block x -> { statements; }
Multiple params, expression (a, b) -> expression
Multiple params, block (a, b) -> { statements; }
Typed params (Type a, Type b) -> expression

Common Patterns

Transformation

// Simple transformation
Function<String, Integer> length = s -> s.length();
Function<String, String> upper = s -> s.toUpperCase();

// Chained transformation
Function<String, String> process = s -> s.trim().toLowerCase();

Filtering

// Simple predicate
Predicate<Integer> isPositive = n -> n > 0;
Predicate<String> notEmpty = s -> !s.isEmpty();

// Complex predicate
Predicate<Person> isAdult = p -> p.getAge() >= 18 && p.hasId();

Combining

// Binary operations
BinaryOperator<Integer> add = (a, b) -> a + b;
BinaryOperator<String> concat = (a, b) -> a + b;
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);

Side Effects

// Consumer for side effects
Consumer<String> log = msg -> System.out.println("[LOG] " + msg);
Consumer<File> delete = f -> f.delete();

Best Practices

1. Keep Lambdas Short

If a lambda is more than 2-3 lines, consider a method reference or a named method:

// Too long - hard to read
list.forEach(item -> {
    validate(item);
    transform(item);
    save(item);
    notify(item);
});

// Better - extract to method
list.forEach(this::processItem);

private void processItem(Item item) {
    validate(item);
    transform(item);
    save(item);
    notify(item);
}

2. Use Meaningful Parameter Names

// Poor - unclear meaning
list.sort((a, b) -> a.getValue() - b.getValue());

// Better - descriptive names
list.sort((first, second) -> first.getValue() - second.getValue());

// Or even more specific
products.sort((product1, product2) -> product1.getPrice() - product2.getPrice());

3. Prefer Expression Bodies

When possible, use expression form over block form:

// Verbose
Predicate<String> notEmpty = s -> {
    return !s.isEmpty();
};

// Concise
Predicate<String> notEmpty = s -> !s.isEmpty();

4. Avoid Complex Logic

// Too complex for a lambda
list.stream()
    .filter(item -> {
        if (item.getType() == Type.A) {
            return item.getValue() > 10;
        } else if (item.getType() == Type.B) {
            return item.getValue() > 20;
        }
        return false;
    });

// Better - extract the logic
list.stream()
    .filter(this::meetsThreshold);

private boolean meetsThreshold(Item item) {
    if (item.getType() == Type.A) {
        return item.getValue() > 10;
    } else if (item.getType() == Type.B) {
        return item.getValue() > 20;
    }
    return false;
}

Lambda syntax is flexible: parentheses optional for single param, types usually inferred, braces optional for single expressions, return implicit for expressions. Use the most concise form that's still readable. Omit parentheses for single parameters. Prefer expression body over block. Let the compiler infer types. Keep lambdas short and focused.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service