1. Lambdas & Functional Interfaces
- Functional interface: exactly one abstract method. Annotate with
@FunctionalInterface(optional but documents intent). - Lambda:
(params) -> expression|block— syntactic sugar for a single-method anonymous class. - Common ones:
Function<T,R>,Predicate<T>,Consumer<T>,Supplier<T>,BiFunction,UnaryOperator,BinaryOperator.
Predicate<String> p = s -> s.length() > 3; // test(T) → boolean
Consumer<String> c = System.out::println; // accept(T) → void
Supplier<Double> sp = Math::random; // get() → T
Function<String, Integer> f = String::length; // apply(T) → R
- Capturing variables must be effectively final — a variable mutated after capture fails to compile.
- Method reference
::is shorthand:ClassName::staticMethod,instance::method,ClassName::new(constructor).
2. The Streams Pipeline
- Source → intermediate ops → terminal op. Intermediate ops are lazy; the pipeline runs only when a terminal op is invoked.
- Intermediate:
filter,map,flatMap,distinct,sorted,limit,peek,skip. - Terminal:
collect,forEach,count,anyMatch/allMatch/noneMatch,findFirst/findAny,reduce,min,max. - Streams are one-shot — consuming a stream twice throws
IllegalStateException. - Streams do not store data — they’re a view over a source.
List<String> names = people.stream()
.filter(p -> p.age() > 18)
.map(Person::name)
.sorted()
.limit(5)
.collect(Collectors.toList());
Short-circuiting: limit, findFirst, anyMatch stop processing early (use with infinite Stream.generate).
3. Intermediate vs Terminal — the interview split
| Operation | Type | Laziness |
|---|---|---|
filter/map/sorted | intermediate | lazy |
distinct | intermediate | lazy |
peek | intermediate | lazy (debug) |
forEach/collect | terminal | eager |
findFirst/anyMatch | terminal | short-circuit |
Gotcha: peek is (unofficially) for debugging; relying on its side effects for output ordering is fragile.
4. Terminal collectors
toList(),toSet(),toMap(...),joining(", "),groupingBy(...),partitioningBy(...),counting,summingInt,maxBy.Collectors.groupingBy(Person::dept)→Map<String, List<Person>>.Collectors.joining(", ")for strings.
5. Optional — the null handler
Optional.of(x)— error if x null.Optional.ofNullable(x)— empty if null.orElse(v)— always evaluates v;orElseGet(supplier)— lazy, only on empty.map,filter,flatMapchain.get()throwsNoSuchElementExceptionif empty — preferorElse.
Gotcha: orElse evaluates the fallback eagerly, so orElseGet is preferred when the fallback is expensive.
6. Common pitfalls
- Boxed loss of precision:
IntStream.range(...)avoids boxed Integer. - Parallel streams — use only when the reduction is associative and stateless; shared mutable state breaks determinism.
.stream()on null collection → NPE — guard withOptional.ofNullable(x).stream().- Sorting stability —
sorted()is stable for equal elements (JDK impl). reduce(identity, BinaryOperator)vscollect— reduce is functional;collectis mutable accumulation (faster for lists).- Shuffle not available in Stream API; use
Collections.shuffle.
One-liner: streams + lambdas are the single highest-leverage “modern Java” interview topic — know the pipeline, the lazy vs terminal split, and the Optional rules cold.
Premium Content
Unlock Part 4: Streams, Lambdas & Functional Java and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans