Lambda Expressions

Anonymous methods that implement a functional interface — turning a block of code into an object

What this chapter is about. A lambda expression, added by JDK 8, is an anonymous method. It is never run on its own; instead it supplies the implementation of the single abstract method of a functional interface. Together with method references, lambdas let you pass executable code as an argument, and they are the foundation for the stream API and easier parallel processing.

1. The Big Idea

Two constructs work together:

Lambda expression
An unnamed method: a parameter list, the arrow operator ->, and a body. It results in a form of anonymous class and is also called a closure.
Functional interface
An interface with exactly one abstract method (a "SAM type" — Single Abstract Method). That method defines the interface's single action, e.g. run() in Runnable.

The functional interface is the lambda's target type. A key rule: a lambda can appear only where a target type is expected — an assignment to a functional-interface reference, a method argument, a return value, a cast, and so on. When a lambda occurs in a target-type context, the compiler automatically creates an instance of a class that implements the interface, with the lambda as the body of its abstract method.

A functional interface may also declare public methods of Object (such as equals()) without losing its status — those are considered implicit members because every instance already implements them. Default, static, and private interface methods (JDK 8+) also do not count against the "one abstract method" rule.

2. Why This Topic Matters

  • Less boilerplate. Where you once wrote a whole anonymous inner class to supply one method, a lambda is a single expression.
  • Code as data. Passing a lambda as an argument passes behavior to a method — a large increase in expressive power.
  • Enables new APIs. Lambdas were the catalyst for default methods, method references, and the stream API, and they make it far easier to exploit multicore hardware.
  • They affect essentially every Java programmer — the book compares their impact to that of generics.

3. Lambda Fundamentals

The lambda (arrow) operator -> splits the expression: parameters on the left, body on the right. Read it as "becomes" or "goes to". There are two body forms — a single expression, or a block in braces.

() -> 123.45 // no parameters; returns a constant () -> Math.random() * 100 // no parameters; returns a computed value (n) -> (n % 2) == 0 // one parameter; returns true if n is even n -> (n % 2) == 0 // parentheses optional for a single parameter (n, d) -> (n % d) == 0 // two parameters
  • An empty parameter list () is used when no parameters are needed.
  • Parameter types are usually inferred from the target's abstract method. You may state them explicitly: (int n) -> (n % 2) == 0. Since JDK 11 you may also use var.
  • All-or-nothing rule for explicit types: (int n, int d) is legal; (int n, d) is not.

4. Functional Interfaces, Step by Step

// A functional interface: exactly one abstract method. interface MyNumber { double getValue(); } class LambdaDemo { public static void main(String[] args) { MyNumber myNum; // functional-interface reference myNum = () -> 123.45; // lambda implements getValue() System.out.println("A fixed value: " + myNum.getValue()); myNum = () -> Math.random() * 100; // a different compatible lambda System.out.println("A random value: " + myNum.getValue()); System.out.println("Another random value: " + myNum.getValue()); // myNum = () -> "123.03"; // ERROR - String is not compatible with double } }
Sample output
A fixed value: 123.45 A random value: 88.90663650412304 Another random value: 53.00582701784129

Reading the code

  • MyNumber myNum; gives a target type. Assigning () -> 123.45 constructs an object whose getValue() returns 123.45.
  • Calling myNum.getValue() executes the lambda.
  • The lambda must be compatible with the abstract method: parameter count and types, the return type, and any thrown exceptions. Returning a String where double is required does not compile.

Parameters and multiple compatible lambdas

interface NumericTest { boolean test(int n); } NumericTest isEven = (n) -> (n % 2) == 0; // n inferred as int from test(int) NumericTest isNonNeg = (n) -> n >= 0; // same interface, different behavior isEven.test(10); // true isNonNeg.test(-1); // false

One functional-interface reference type can hold any lambda compatible with it. Here isEven and isNonNeg are both NumericTest values.

5. Block Lambdas

When one expression is not enough, use a block body in braces — it may declare variables, loop, branch, and nest blocks, just like a method body. You must use an explicit return to return a value.

interface NumericFunc { int func(int n); } NumericFunc factorial = (n) -> { int result = 1; for(int i = 1; i <= n; i++) result = i * result; return result; // returns from the LAMBDA, not an enclosing method }; factorial.func(3); // 6 factorial.func(5); // 120
A return inside a lambda returns only from the lambda. It does not cause the surrounding method to return.
interface StringFunc { String func(String n); } StringFunc reverse = (str) -> { // str inferred as String String result = ""; for(int i = str.length() - 1; i >= 0; i--) result += str.charAt(i); // charAt() is legal - str is known to be String return result; }; reverse.func("Lambda"); // "adbmaL"

6. Generic Functional Interfaces

A lambda itself cannot declare type parameters (it cannot be generic). But the interface it targets can be, which removes the need for near-duplicate interfaces that differ only in data type.

interface SomeFunc<T> { T func(T t); } SomeFunc<String> reverse = (str) -> { /* ...reverse... */ return result; }; SomeFunc<Integer> factorial = (n) -> { /* ...factorial... */ return result; }; reverse.func("Lambda"); // uses the String instantiation factorial.func(5); // uses the Integer instantiation

Here T is both the parameter type and the return type of func(), so SomeFunc<T> is compatible with any lambda that takes one argument and returns a value of the same type. Only the type argument differs between the two uses.

7. Passing Lambda Expressions as Arguments

To pass a lambda as an argument, the receiving parameter's type must be a compatible functional interface. This is one of the most common and most powerful uses of lambdas.

interface StringFunc { String func(String n); } class LambdasAsArgumentsDemo { // First parameter is a functional interface, so it can receive a lambda. static String stringOp(StringFunc sf, String s) { return sf.func(s); } public static void main(String[] args) { String inStr = "Lambdas add power to Java"; // (a) simple expression lambda inline String out = stringOp((str) -> str.toUpperCase(), inStr); // (b) block lambda inline out = stringOp((str) -> { String result = ""; for(int i = 0; i < str.length(); i++) if(str.charAt(i) != ' ') result += str.charAt(i); return result; }, inStr); // (c) a lambda assigned earlier, then passed by name StringFunc reverse = (str) -> { String result = ""; for(int i = str.length() - 1; i >= 0; i--) result += str.charAt(i); return result; }; out = stringOp(reverse, inStr); } }
Expected output
The string in uppercase: LAMBDAS ADD POWER TO JAVA The string with spaces removed: LambdasaddpowertoJava The string reversed: avaJ ot rewop dda sadbmaL

Inline simple lambdas are convenient for one-off use; a long block lambda reads better assigned to a variable first. Besides assignment and argument passing, other target-type contexts include casts, the ? operator, array initializers, and return statements.

8. Lambdas and Exceptions

A lambda may throw an exception, but a checked exception must be listed in the throws clause of the functional interface's abstract method.

interface DoubleNumericArrayFunc { double func(double[] n) throws EmptyArrayException; // throws clause required } class EmptyArrayException extends Exception { EmptyArrayException() { super("Array Empty"); } } DoubleNumericArrayFunc average = (n) -> { if(n.length == 0) throw new EmptyArrayException(); double sum = 0; for(int i = 0; i < n.length; i++) sum += n[i]; return sum / n.length; }; average.func(new double[]{1,2,3,4}); // 2.5 average.func(new double[0]); // throws EmptyArrayException

Without the throws clause on func(), the program would not compile, because the lambda would no longer be compatible.

The parameter of func() is an array (double[]), so the lambda parameter is written simply as n — its type is inferred as double[] from the target context. Writing n[] is not legal; writing double[] n is legal but adds nothing.

9. Variable Capture

  • A lambda may freely use and modify instance and static variables of its enclosing class, and it has access to this (the lambda has no this of its own — this refers to the enclosing instance).
  • A lambda may use a local variable of the enclosing scope only if that variable is effectively final — its value never changes after first assignment. It may not modify such a variable (doing so would remove its effectively-final status).
int num = 10; // effectively final MyFunc myLambda = (n) -> { int v = num + n; // OK - reads num // num++; // ERROR - would modify a captured local return v; }; // num = 9; // ERROR - also removes effectively-final status

10. Method References

A method reference refers to a method without executing it. Like a lambda, it needs a compatible functional-interface target type, and evaluating it creates an instance of that interface. The separator is the double colon :: (new in JDK 8).

KindSyntaxMeaning
Static methodClassName::methodNameRefers to a static method.
Instance method of a specific objectobjRef::methodNameThe method is always called on that object.
Instance method of any object of a classClassName::instanceMethodNameFirst functional-interface parameter is the receiver; the rest map to the method's parameters.
Superclass versionsuper::name or typeName.super::nameRefers to the superclass / super-interface method.
ConstructorClassName::newRefers to a constructor (see Section 11).
Array constructortype[]::newFunctional interface must take a single int (the length).

10.1 Static method reference

interface StringFunc { String func(String n); } class MyStringOps { static String strReverse(String str) { String result = ""; for(int i = str.length() - 1; i >= 0; i--) result += str.charAt(i); return result; } } // MyStringOps::strReverse is compatible with StringFunc.func(String) outStr = stringOp(MyStringOps::strReverse, inStr); // "avaJ ot rewop dda sadbmaL"

10.2 Instance method reference

MyStringOps strOps = new MyStringOps(); outStr = stringOp(strOps::strReverse, inStr); // strReverse() called on strOps

10.3 Instance method of any object — ClassName::instanceMethod

interface MyFunc<T> { boolean func(T v1, T v2); } class HighTemp { private int hTemp; HighTemp(int ht) { hTemp = ht; } boolean sameTemp(HighTemp ht2) { return hTemp == ht2.hTemp; } boolean lessThanTemp(HighTemp ht2) { return hTemp < ht2.hTemp; } } static <T> int counter(T[] vals, MyFunc<T> f, T v) { int count = 0; for(int i = 0; i < vals.length; i++) if(f.func(vals[i], v)) count++; return count; } counter(weekDayHighs, HighTemp::sameTemp, new HighTemp(89)); // 3 counter(weekDayHighs, HighTemp::lessThanTemp, new HighTemp(89)); // 3

In HighTemp::sameTemp, the functional interface's first parameter maps to the invoking object (HighTemp) and the second to sameTemp()'s own parameter.

10.4 Method references with generics

// Generic method in a non-generic class: count = myOp(MyArrayOps::<Integer>countMatching, vals, 4);

For a generic method, the type argument goes after :: and before the method name. For a generic class, it follows the class name and precedes ::. Usually inference makes it unnecessary.

10.5 A practical use with the Collections Framework

class MyClass { private int val; MyClass(int v) { val = v; } int getVal() { return val; } } class UseMethodRef { // Compatible with Comparator<T>.compare(T, T). static int compareMC(MyClass a, MyClass b) { return a.getVal() - b.getVal(); } public static void main(String[] args) { ArrayList<MyClass> al = new ArrayList<MyClass>(); // ... add MyClass(1,4,2,9,3,7) ... MyClass maxValObj = Collections.max(al, UseMethodRef::compareMC); System.out.println("Maximum value is: " + maxValObj.getVal()); // 9 } }

Before JDK 8 you had to write a class implementing Comparator and instantiate it. Now a reference to a compatible comparison method is enough — it automatically implements the comparator.

11. Constructor References

interface MyFunc { MyClass func(int n); } class MyClass { private int val; MyClass(int v) { val = v; } // parameterized constructor MyClass() { val = 0; } // default constructor int getVal() { return val; } } // "new" resolves to the constructor whose signature matches func(int). MyFunc myClassCons = MyClass::new; MyClass mc = myClassCons.func(100); // same as new MyClass(100)
  • ClassName::new yields a constructor reference; the compiler picks the constructor matching the functional interface's method.
  • For a generic class: MyFunc<Integer> cons = MyClass<Integer>::new;.
  • Array form: MyClass[]::new with a functional interface whose method takes one int (the length): cons.func(2) makes a 2-element array.

A more realistic use: a class factory

interface MyFunc<R, T> { R func(T n); } static <R, T> R myClassFactory(MyFunc<R, T> cons, T v) { return cons.func(v); } MyFunc<MyClass<Double>, Double> c1 = MyClass<Double>::new; MyClass<Double> mc = myClassFactory(c1, 100.1); MyFunc<MyClass2, String> c2 = MyClass2::new; MyClass2 mc2 = myClassFactory(c2, "Lambda");

myClassFactory() can build an object of any class whose constructor is compatible with func() — generic or not.

12. Predefined Functional Interfaces (java.util.function)

You often need not write your own functional interface. The java.util.function package supplies many; a sampling:

InterfacePurposeAbstract method
UnaryOperator<T>Apply a unary operation to a T, return a Tapply()
BinaryOperator<T>Apply an operation to two T values, return a Tapply()
Consumer<T>Apply an operation to a T (no result)accept()
Supplier<T>Return a T (no input)get()
Function<T, R>Apply an operation to a T, return an Rapply()
Predicate<T>Test a T against a constraint, return booleantest()
import java.util.function.Function; Function<Integer, Integer> factorial = (n) -> { int result = 1; for(int i = 1; i <= n; i++) result = i * result; return result; }; factorial.apply(3); // 6 factorial.apply(5); // 120

13. Visual Mental Model

TARGET-TYPE CONTEXT (assignment, argument, return, cast, ?: , array init) | v functional interface I { R m(A a); } <- exactly ONE abstract method ^ | supplies the body of m() | lambda (a) -> expr or (a) -> { ... return ...; } method ref Class::m / obj::m / Class::instM / Class::new evaluating the lambda / method reference | v a new object that implements I ; calling I.m(...) runs the lambda body

14. Important Comparisons

Expression lambdaBlock lambda
BodyA single expression{ ... } with any statements
Returning a valueImplicit (the expression's value)Explicit return required
LambdaAnonymous inner class
Syntax weightMinimalFull class body
thisThe enclosing instance (no own this)The anonymous object itself
Can it be generic?No (type parameters not allowed)N/A
LambdaMethod reference
Defines new behaviorYesNo — reuses an existing method
When to preferLogic that does not already exist as a methodWhen a method already does exactly what is needed
Instance/static field of enclosing classLocal variable of enclosing scope
ReadAllowedAllowed only if effectively final
ModifyAllowedNot allowed

15. Common Beginner Mistakes

Using a lambda where there is no target type — e.g. assigning it to Object or var with nothing to infer from.
Incompatible lambda — wrong parameter count/types, wrong return type, or a checked exception not in the interface's throws.
Forgetting return in a block lambda that must produce a value.
Thinking return in a lambda returns from the enclosing method. It returns from the lambda only.
Modifying a captured local variable (or reassigning it outside), breaking effectively-final status.
Mixing explicit and inferred parameter types(int n, d).
Writing the array parameter as n[] instead of n.
Trying to give the lambda its own type parameters — make the interface generic instead.

16. Best Practices

  • Prefer a lambda or method reference over an anonymous inner class for single-method interfaces.
  • Use a method reference when a method already does the job (Class::method, obj::method, Class::new) — it is shorter and self-documenting.
  • Keep lambdas small. If a block lambda grows large, extract it to a named method and reference it.
  • Let parameter types be inferred unless an explicit type genuinely aids clarity or is required.
  • Reuse the predefined interfaces in java.util.function (Predicate, Function, Consumer, Supplier, …) instead of inventing near-duplicates; make an interface generic when only the data type varies.
  • Only capture effectively-final locals; if you need mutable state, use a field or a single-element array/holder deliberately.
  • Assign a long block lambda to a well-named variable before passing it, rather than embedding it in a call.

17. "Remember" Points

Key takeaways
  • A lambda is an anonymous method that implements the single abstract method of a functional interface (its target type).
  • It can appear only in a target-type context (assignment, argument, return, cast, ?:, array initializer).
  • -> separates parameters from body; body is one expression or a block (block needs explicit return).
  • Parameter types are normally inferred; explicit types are all-or-nothing.
  • A lambda cannot be generic, but its interface can.
  • Checked exceptions thrown must be declared by the interface method's throws.
  • Lambdas capture effectively-final locals (read only); they can read/write enclosing fields and see the enclosing this.
  • Method references (::) reuse an existing method or constructor as a functional-interface instance.
  • java.util.function supplies ready-made functional interfaces.

18. Interview / Revision Questions

  • Define "lambda expression" and "functional interface". What is a SAM type?
  • What is a target type, and name four target-type contexts.
  • Contrast an expression lambda and a block lambda. When is an explicit return required?
  • What does it mean for a lambda to be "compatible" with an abstract method?
  • Can a lambda declare type parameters? How do you make lambda code work for many types?
  • What must be true of the functional interface method if a lambda throws a checked exception?
  • Explain variable capture and "effectively final". What can a lambda do with enclosing instance fields vs. enclosing local variables?
  • Where does a return statement inside a lambda transfer control?
  • List the method-reference forms and what each refers to. What does :: do?
  • For ClassName::instanceMethod, how do the functional interface's parameters map onto the call?
  • Show how ClassName::new selects which constructor to use.
  • Name the six predefined interfaces from the chapter and each one's method.
  • Rewrite an anonymous Comparator that compares two objects by an int field as (a) a lambda and (b) a method reference passed to Collections.max().

19. Practice Exercises

  • Basics: Declare interface IntTest { boolean test(int n); } and create lambdas for "is prime", "is a perfect square", and "is negative". Run each through an IntTest reference.
  • Block lambda: Implement interface StrFn { String apply(String s); } with a block lambda that title-cases each word.
  • Generic functional interface: Write interface SomeFunc<T> { T func(T t); } and use one reference type for a String reverser and an Integer doubler.
  • Code as data: Write static String stringOp(StringFunc f, String s) and call it with an inline lambda, a block lambda, and a pre-assigned lambda.
  • Exceptions: Reproduce DoubleNumericArrayFunc with a throws EmptyArrayException clause; verify it fails to compile if you remove the clause.
  • Capture: Prove that modifying a captured local (num++) inside a lambda does not compile, but modifying an instance field does.
  • Method references: Rewrite a string-reversing lambda as a static method reference, then as an instance method reference on a specific object.
  • Constructor reference: Given MyClass(int), create MyFunc with MyClass func(int) and build instances via MyClass::new. Then build a MyClass[] via MyClass[]::new.
  • Predefined interfaces: Redo the factorial example with Function<Integer,Integer>; write a Predicate<String> for "longer than 5 chars" and a Supplier<Double> that returns a random value.

20. Quick Memory Map

LAMBDA EXPRESSIONS |-- Lambda = anonymous method; implements the ONE abstract method of a | functional interface (its TARGET TYPE); a.k.a. closure | |-- Syntax: (params) -> expression (params) -> { statements; return x; } | () no params | (n) one | (n, d) many | types inferred; explicit = all-or-nothing; var allowed (JDK 11) | |-- Functional interface = 1 abstract method (SAM); default/static/private | and Object public methods don't count | |-- Contexts: assignment, argument, return, cast, ?: , array initializer | |-- Block lambda: needs explicit return; return exits the LAMBDA only |-- Generic: lambda can't be generic; the interface can interface F<T>{ T f(T t); } |-- Exceptions: checked ones must be in the method's throws clause |-- Capture: enclosing fields = read/write ; enclosing locals = effectively final, read-only | |-- Method references (::) | Class::staticM obj::instanceM Class::instanceM(any obj) Class::new type[]::new | super::m / Type.super::m ; generics: Class::<T>m or Class<T>::m | `-- java.util.function: UnaryOperator, BinaryOperator, Consumer(accept), Supplier(get), Function<T,R>(apply), Predicate<T>(test)

21. Complete Chapter Revision

  1. A lambda expression is an anonymous method; it supplies the body of the single abstract method of a functional interface, which is the lambda's target type.
  2. A lambda can appear only where a target type is expected: assignment to a functional-interface reference, method argument, return value, cast, ?:, or array initializer.
  3. The -> operator separates parameters (left) from the body (right). Bodies are either a single expression or a block; a block requires an explicit return and its return exits only the lambda.
  4. Parameter types are usually inferred from the abstract method; explicit types must be given for all parameters or none.
  5. One functional-interface reference type can hold any compatible lambda; "compatible" means matching parameters, return type, and thrown exceptions.
  6. A lambda cannot be generic, but the functional interface can, letting one interface serve many data types.
  7. A lambda that throws a checked exception requires that exception in the interface method's throws clause.
  8. Variable capture: lambdas may read/write enclosing instance/static variables and use the enclosing this; they may only read effectively-final enclosing locals.
  9. Method references (::) name an existing method or constructor as a functional-interface instance: Class::staticM, obj::instanceM, Class::instanceM (receiver is the first parameter), super::m, Class::new, and type[]::new. Generic type arguments go after :: for methods, before it for classes.
  10. Method references shine with the Collections Framework — e.g. passing a comparison method to Collections.max() instead of implementing Comparator.
  11. Constructor references pick the constructor matching the functional interface's method; a class factory that receives a constructor reference can build any compatible type.
  12. The java.util.function package provides ready-made functional interfaces: UnaryOperator, BinaryOperator, Consumer, Supplier, Function<T,R>, Predicate<T>.

22. Final Takeaway

Lambda expressions let you write behavior in place — a short, unnamed method that becomes an object implementing a one-method interface. Provide a target type, keep the body small, prefer a method reference when a method already exists, capture only effectively-final locals, and lean on java.util.function instead of hand-rolling interfaces. This is the mechanism that makes Java's stream API and easy parallelism possible.