Working with Lambda Expressions

1. Creating Lambda Syntax

FormSyntaxUse Case
Zero params() -> exprRunnable, Supplier
Single paramx -> exprFunction, Consumer
Multi params(a, b) -> exprBiFunction, Comparator
Typed params(int a, int b) -> a + bDisambiguation
Block body(x) -> { stmt; return v; }Multi-statement
var params Java 11+(@NotNull var x) -> xAnnotated lambda params

Example: Common lambda forms

Runnable r = () -> System.out.println("run");
Function<String, Integer> len = s -> s.length();
Comparator<User> byAge = (a, b) -> Integer.compare(a.age(), b.age());
BinaryOperator<Integer> sum = (a, b) -> { int t = a + b; return t; };

2. Using Functional Interfaces (@FunctionalInterface)

InterfaceSignaturePurpose
Function<T,R>R apply(T)Transform
BiFunction<T,U,R>R apply(T,U)Two-arg transform
Predicate<T>boolean test(T)Filter/match
Consumer<T>void accept(T)Side effect
Supplier<T>T get()Lazy producer
UnaryOperator<T>T apply(T)Same-type transform
BinaryOperator<T>T apply(T,T)Reduce/combine
IntFunction<R>R apply(int)Primitive specialization

Example: Custom functional interface

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}
ThrowingFunction<String, Integer, IOException> parser = Files::size;

3. Understanding Method References (::)

KindSyntaxEquivalent Lambda
StaticInteger::parseInts -> Integer.parseInt(s)
Bound instanceSystem.out::printlnx -> System.out.println(x)
Unbound instanceString::lengths -> s.length()
ConstructorArrayList::new() -> new ArrayList<>()
Array constructorString[]::newn -> new String[n]

4. Using Constructor References (Class::new)

Target TypeReferenceCalls
Supplier<User>User::newnew User()
Function<String,User>User::newnew User(name)
BiFunction<String,Integer,User>User::newnew User(name,age)
IntFunction<int[]>int[]::newnew int[n]

Example: Stream toArray with constructor ref

String[] arr = list.stream().map(String::toUpperCase).toArray(String[]::new);
List<User> users = names.stream().map(User::new).toList();

5. Capturing Variables

Variable TypeCapture RuleNotes
LocalMust be effectively finalCompile error otherwise
Instance fieldCaptured via thisMutation allowed
Static fieldCaptured by classMutation allowed
Method paramEffectively finalSame as locals
Warning: Capturing mutable references (e.g., AtomicInteger) sidesteps the rule but introduces shared state — use cautiously in parallel streams.

6. Understanding Lambda Scope

AspectLambdaAnonymous Class
thisEnclosing instanceAnonymous instance
ShadowingCannot shadow enclosing namesCan shadow
ScopeLexical (enclosing method)New scope
Bytecodeinvokedynamic + LambdaMetafactoryGenerated .class file

7. Handling Exceptions in Lambdas

PatternApproachTrade-off
Wrap as runtimetry/catch + throw new RuntimeExceptionLoses checked semantics
Throwing FICustom @FunctionalInterface with throwsType-safe propagation
Sneaky throwsGeneric trick to throw checkedHides exceptions
Result/EitherReturn wrapper instead of throwingFunctional, verbose

Example: Wrapper utility

static <T, R> Function<T, R> unchecked(ThrowingFunction<T, R, ?> f) {
    return t -> { try { return f.apply(t); }
                   catch (Exception e) { throw new RuntimeException(e); } };
}
paths.stream().map(unchecked(Files::readString)).toList();

8. Using Lambda with Collections

MethodSignatureUse
forEachConsumer<T>Iterate
removeIfPredicate<T>Conditional removal
replaceAllUnaryOperator<T>In-place transform
Map.computeBiFunction<K,V,V>Atomic update
Map.mergeBiFunction<V,V,V>Combine on key collision
sortComparator<T>In-place sort

9. Creating Custom Functional Interfaces

RuleDetail
Single abstract methodRequired (SAM)
default methodsAllowed, unlimited
static methodsAllowed
Object overridesDon't count toward SAM
@FunctionalInterfaceOptional, enforces SAM

Example: Tri-function

@FunctionalInterface
interface TriFunction<A, B, C, R> {
    R apply(A a, B b, C c);
    default <V> TriFunction<A, B, C, V> andThen(Function<? super R, ? extends V> after) {
        return (a, b, c) -> after.apply(apply(a, b, c));
    }
}

10. Composing Functions

MethodOrderExample
andThenthis → afterf.andThen(g).apply(x) == g(f(x))
composebefore → thisf.compose(g).apply(x) == f(g(x))
Function.identity()x → xReduce starting value

Example: Pipeline composition

Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, Integer> len = String::length;
Function<String, Integer> pipeline = trim.andThen(upper).andThen(len);
pipeline.apply("  hello "); // 5

11. Using Predicate Composition

MethodLogicUse
andShort-circuit ANDCombine filters
orShort-circuit ORMatch any
negateLogical NOTInvert
Predicate.not(p) Java 11+Static negationMethod ref friendly
Predicate.isEqual(o)Objects.equalsEquality test

Example: Filter chain

Predicate<User> adult = u -> u.age() >= 18;
Predicate<User> active = User::isActive;
users.stream().filter(adult.and(active).and(Predicate.not(User::isBanned))).toList();

12. Understanding Lambda Performance Characteristics

AspectBehaviorNote
Bootstrapinvokedynamic + LambdaMetafactoryOne-time per call site
Non-capturingSingleton instance reusedZero allocation
CapturingNew instance per invocationAvoid in hot loops
Method refSame as lambdaOften inlined by JIT
BoxingGeneric FIs box primitivesUse IntFunction etc.
Note: Modern HotSpot JIT inlines small lambdas at megamorphic call sites; benchmark before optimizing.