Predict the output · Java
Scope, closures and lambdas
Which variable a name refers to, and what a closure captures.
Learn: Scope, lambdas and captured variables in Java7 minute read
Scope is the part of a program where a name can be used. Scope questions ask which variable a name refers to and how long it lives. Lambda questions add one more rule: what a lambda (a short anonymous function such as x -> x * 2) is allowed to capture from the code around it. Testers meet these rules in data-driven tests, stream filters (code that picks items from a list with a stream) and listener code (code that reacts to an event), so interviewers check them. Java keeps them strict, which makes the output easy to predict.
Rule 1: a local variable lives inside its block
A block is the code between a pair of braces. A variable declared inside a block exists only until the closing brace. A variable declared in a loop body is created fresh on every pass, and the loop variable of a for loop is gone after the loop, so a new variable with the same name can be declared later. Java does not allow a local variable to shadow (hide) another local in an enclosing block (a block around it) of the same method, so that kind of confusion does not even compile: Java refuses to build the program.
Rule 2: locals and parameters shadow fields
A local variable or parameter with the same name as a field hides the field. This is called shadowing: plain x then means the local. Use this.x for an instance field or ClassName.x for a static field. The classic bug is size = size; in a constructor: it assigns the parameter to itself, and the field keeps its default value.
Rule 3: lifetime depends on where a variable is declared
A local variable is created again on each method call, so it never remembers anything between calls. An instance field lives as long as its object, and a static field lives as long as the class, shared by every object.
Rule 4: lambdas capture effectively final values
A lambda may use a local variable from the surrounding method only if that variable is effectively final, meaning it is never assigned again after it gets its value. The lambda captures that value. Inside a loop, declare a new variable on each pass (int j = i;) to give each lambda its own value. Fields are different: a lambda reaches them through this (or the class), so it can read and change them freely. A one-element array is a common way to let a lambda change a counter, because the array reference never changes. An AtomicInteger, a class that holds a number which can be changed safely, works too.
import java.util.*;
import java.util.function.*;
public class Main {
static int total = 100;
public static void main(String[] args) {
int total = 1;
List<IntSupplier> list = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
int step = i * total;
list.add(() -> step);
}
for (IntSupplier s : list) {
System.out.print(s.getAsInt() + " ");
}
System.out.println();
System.out.println(total + " " + Main.total);
}
}
1 2 3 1 100
Here the local total shadows the static field, each lambda captures its own step, and Main.total still reaches the field.
Rule 5: this in a lambda is the enclosing object
An anonymous class is a class written without a name, right where it is used. A lambda does not start a new scope for this; inside it, this is the object whose method created the lambda. An anonymous class is a real class, so this inside it is the anonymous object, and Outer.this reaches the enclosing one.
Rule 6: method references fix their target early
A method reference is a short way to write a lambda that calls one method. In name::toUpperCase, the expression before :: is evaluated once, when the reference is created, and kept. Pointing name at another object later does not change what the reference calls.
Rule 7: streams are lazy
Intermediate operations such as map and filter only describe work. Nothing runs until a terminal operation (the final step that produces a result) such as collect or findFirst, and then elements flow through one at a time. Short-circuiting steps such as limit and findFirst stop early, so later elements are never processed.
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
Stream<String> s = Stream.of("a", "b", "c").map(x -> {
System.out.println("map " + x);
return x.toUpperCase();
});
System.out.println("built");
System.out.println(s.limit(2).collect(Collectors.toList()));
}
}
built map a map b [A, B]
How to answer in an interview
For each name in the snippet, say which declaration it refers to before you trace values. If asked why a lambda cannot change a local counter, answer directly: "Java lambdas capture the value of effectively final locals, so assigning to the local is a compile error (Java refuses to build the program). I would use a field, an array element or an AtomicInteger." Mention that this rule is what protects Java from the late binding surprise that Python has.
Common mistakes
- Writing
size = size;and expecting the field to change. - Expecting a local variable to keep its value between calls.
- Thinking a lambda's
thisis the lambda itself. - Expecting a stream's
mapto print before a terminal step runs.
Recap
Names resolve to the closest declaration, and this. or the class name reaches a hidden field. Locals restart on every call; static fields are shared. Lambdas capture effectively final values and keep the outer this, method references fix their target when created, and streams do nothing until a terminal step.
9 questions0/9 solved
| Status | Question | Difficulty | Asked in |
|---|---|---|---|
| Not started | A local variable with the same name as a field | Easy | |
| Not started | Variables that live only inside a loop | Easy | |
| Pro+ | size = size in a constructor | Easy-Medium | |
| Pro+ | A local variable versus a static field across calls | Easy-Medium | |
| Pro+ | Lambdas created inside a loop | Medium | |
| Pro+ | Counting inside a lambda | Medium | |
| Pro+ | this inside a lambda and an anonymous class | Medium-Hard | |
| Pro+ | When does a stream filter actually run? | Medium-Hard | |
| Pro+ | A method reference keeps the old object | Hard |
The first 2 questions of each topic are free. Pro+ opens every question.