Working with Lambda Expressions
1. Creating Lambda Syntax
Form Syntax Use Case
Zero params () -> exprRunnable, Supplier
Single param x -> 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
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)
Interface Signature Purpose
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 (::)
Kind Syntax Equivalent Lambda
Static Integer::parseInts -> Integer.parseInt(s)
Bound instance System.out::printlnx -> System.out.println(x)
Unbound instance String::lengths -> s.length()
Constructor ArrayList::new() -> new ArrayList<>()
Array constructor String[]::newn -> new String[n]
4. Using Constructor References (Class::new)
Target Type Reference Calls
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 Type Capture Rule Notes
Local Must be effectively final Compile error otherwise
Instance field Captured via this Mutation allowed
Static field Captured by class Mutation allowed
Method param Effectively final Same 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
Aspect Lambda Anonymous Class
thisEnclosing instance Anonymous instance
Shadowing Cannot shadow enclosing names Can shadow
Scope Lexical (enclosing method) New scope
Bytecode invokedynamic + LambdaMetafactoryGenerated .class file
7. Handling Exceptions in Lambdas
Pattern Approach Trade-off
Wrap as runtime try/catch + throw new RuntimeException Loses checked semantics
Throwing FI Custom @FunctionalInterface with throws Type-safe propagation
Sneaky throws Generic trick to throw checked Hides exceptions
Result/Either Return wrapper instead of throwing Functional, 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
Method Signature Use
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
Rule Detail
Single abstract method Required (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
Method Order Example
andThenthis → after f.andThen(g).apply(x) == g(f(x))
composebefore → this f.compose(g).apply(x) == f(g(x))
Function.identity()x → x Reduce 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
Method Logic Use
andShort-circuit AND Combine filters
orShort-circuit OR Match any
negateLogical NOT Invert
Predicate.not(p) Java 11+ Static negation Method 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 ();
Aspect Behavior Note
Bootstrap invokedynamic + LambdaMetafactoryOne-time per call site
Non-capturing Singleton instance reused Zero allocation
Capturing New instance per invocation Avoid in hot loops
Method ref Same as lambda Often inlined by JIT
Boxing Generic FIs box primitives Use IntFunction etc.
Note: Modern HotSpot JIT inlines small lambdas at megamorphic call sites; benchmark before optimizing.