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).
| Kind | Syntax | Meaning |
| Static method | ClassName::methodName | Refers to a static method. |
| Instance method of a specific object | objRef::methodName | The method is always called on that object. |
| Instance method of any object of a class | ClassName::instanceMethodName | First functional-interface parameter is the receiver; the rest map to the method's parameters. |
| Superclass version | super::name or typeName.super::name | Refers to the superclass / super-interface method. |
| Constructor | ClassName::new | Refers to a constructor (see Section 11). |
| Array constructor | type[]::new | Functional 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:
| Interface | Purpose | Abstract method |
| UnaryOperator<T> | Apply a unary operation to a T, return a T | apply() |
| BinaryOperator<T> | Apply an operation to two T values, return a T | apply() |
| 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 R | apply() |
| Predicate<T> | Test a T against a constraint, return boolean | test() |
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 lambda | Block lambda |
| Body | A single expression | { ... } with any statements |
| Returning a value | Implicit (the expression's value) | Explicit return required |
| Lambda | Anonymous inner class |
| Syntax weight | Minimal | Full class body |
| this | The enclosing instance (no own this) | The anonymous object itself |
| Can it be generic? | No (type parameters not allowed) | N/A |
| Lambda | Method reference |
| Defines new behavior | Yes | No — reuses an existing method |
| When to prefer | Logic that does not already exist as a method | When a method already does exactly what is needed |
| Instance/static field of enclosing class | Local variable of enclosing scope |
| Read | Allowed | Allowed only if effectively final |
| Modify | Allowed | Not 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
- 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.
- 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.
- 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.
- Parameter types are usually inferred from the abstract method; explicit types must be given for all parameters or none.
- One functional-interface reference type can hold any compatible lambda; "compatible" means matching parameters, return type, and thrown exceptions.
- A lambda cannot be generic, but the functional interface can, letting one interface serve many data types.
- A lambda that throws a checked exception requires that exception in the interface method's throws clause.
- Variable capture: lambdas may read/write enclosing instance/static variables and use the enclosing this; they may only read effectively-final enclosing locals.
- 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.
- Method references shine with the Collections Framework — e.g. passing a comparison method to Collections.max() instead of implementing Comparator.
- Constructor references pick the constructor matching the functional interface's method; a class factory that receives a constructor reference can build any compatible type.
- 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.