iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Lambdas

Java lambdas are anonymous functions matching a functional interface. Pair with method references and the Streams API for declarative collection processing.

Functional interfaces, methods refs, capture

EXAMPLE
import java.util.*;
import java.util.function.*;
import java.util.stream.*;

// 1) Basic lambda
Runnable r = () -> System.out.println("hi");
r.run();

Function<Integer, Integer> square = x -> x * x;
square.apply(5);                     // 25

BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
add.apply(2, 3);                     // 5

Predicate<String> isLong = s -> s.length() > 8;
isLong.test("hello");                // false

Consumer<String> print = System.out::println;
print.accept("hi");

Supplier<List<String>> mk = ArrayList::new;
List<String> list = mk.get();

// 2) Functional interfaces — the contracts
//   Runnable       : void ()
//   Callable<V>    : V ()           — throws Exception
//   Supplier<T>    : T ()
//   Consumer<T>    : void (T)
//   BiConsumer<T,U>: void (T, U)
//   Predicate<T>   : boolean (T)
//   Function<T,R>  : R (T)
//   BiFunction<T,U,R> : R (T, U)
//   UnaryOperator<T>  : T (T)
//   BinaryOperator<T> : T (T, T)

// 3) Method references
Function<String, Integer> len = String::length;             // instance method on class
Function<List<?>, Integer> size = List::size;
BiFunction<String, String, String> concat = String::concat; // bound to first arg

Consumer<String> printer = System.out::println;             // bound instance method
Supplier<List<String>> ctor = ArrayList::new;               // constructor reference

// 4) Lambda + Stream — the daily driver
List<Integer> nums = List.of(1, 2, 3, 4, 5);

int sum = nums.stream().mapToInt(Integer::intValue).sum();
List<Integer> squares = nums.stream().map(n -> n * n).toList();
List<Integer> evens   = nums.stream().filter(n -> n % 2 == 0).toList();

Optional<Integer> max = nums.stream().max(Integer::compare);
Map<Boolean, List<Integer>> partitioned = nums.stream().collect(Collectors.partitioningBy(n -> n > 2));

// 5) Capture — lambdas can use surrounding variables (effectively final)
int base = 10;
Function<Integer, Integer> plusBase = x -> x + base;
// base = 20;       // ILLEGAL — captured variable must be effectively final

// To 'capture mutable state', use an array/AtomicInteger/lambda-local class
int[] counter = {0};
nums.forEach(n -> counter[0]++);

// 6) Lambda in a sort
List<String> names = new ArrayList<>(List.of("Cy", "Ada", "Bo"));
names.sort((a, b) -> a.length() - b.length());
names.sort(Comparator.comparingInt(String::length));
names.sort(Comparator.comparing(String::length).thenComparing(Comparator.naturalOrder()));

// 7) Multi-line / block lambdas
Function<String, String> upperGreet = s -> {
    if (s == null || s.isBlank()) return "Hello, stranger";
    return "Hello, " + s.trim().toUpperCase();
};

// 8) Define your own functional interface
@@FunctionalInterface
interface TriFunction<A, B, C, R> {
    R apply(A a, B b, C c);
}

TriFunction<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;

// 9) Higher-order — functions that take or return lambdas
static <T> List<T> filter(List<T> in, Predicate<T> p) {
    var out = new ArrayList<T>();
    for (var x : in) if (p.test(x)) out.add(x);
    return out;
}

filter(nums, n -> n > 2);

// 10) Composition
Function<Integer, Integer> plus1   = x -> x + 1;
Function<Integer, Integer> times2  = x -> x * 2;
plus1.andThen(times2).apply(3);     // (3+1)*2 = 8
plus1.compose(times2).apply(3);     // (3*2)+1 = 7

Predicate<Integer> small = n -> n < 5;
Predicate<Integer> even  = n -> n % 2 == 0;
small.and(even).test(4);            // true
small.or(even).test(7);             // false
small.negate().test(6);             // true

// 11) Optional + lambda
Optional<String> name = Optional.of("Ada");
name.map(String::toUpperCase).ifPresent(System.out::println);
String display = name.orElseGet(() -> "Anonymous");

// 12) Exception handling in lambdas (clunky for checked)
Function<String, Integer> parse = s -> {
    try { return Integer.parseInt(s); }
    catch (NumberFormatException e) { return 0; }
};

// Helper to bridge — wrap a throwing lambda
@@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
    return t -> {
        try { return f.apply(t); }
        catch (Exception e) { throw new RuntimeException(e); }
    };
}

List<Integer> parsed = strings.stream().map(unchecked(Integer::parseInt)).toList();

// 13) Common pitfalls
//   • Capturing 'this' implicitly — keeps the enclosing object from GC; risky in long-lived lambdas
//   • Forgetting effectively-final → won't compile
//   • Using lambdas instead of for loops in HOT inner loops — JIT usually handles it, but profile
//   • Lambdas with checked exceptions — wrap or rethrow as runtime

Why it matters

Method references (String::length, System.out::println) are the cleanest lambda form. Combined with Streams, the “Java is verbose” trope mostly disappears.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
Runnable hi = () -> System.out.println("hi");
hi.run();

List<String> xs = List.of("b","a");
xs.stream().sorted().forEach(System.out::println);
Try it Yourself »

Discussion

Loading…