Modern Java (Java 8+)
Java 8 (2014) changed what idiomatic code looks like: behaviour is now passed as lambdas rather than bulky anonymous classes, collections are processed through the declarative Stream API, and absence is expressed by the Optional type instead of null. This is not a bag of syntactic sugar — it is a different way of thinking about data and computation.
One chain runs through the whole topic. A functional interface (exactly one abstract method) is the type a lambda binds to. Streams are built from lambdas and are lazy: intermediate operations compute nothing until a terminal operation arrives. map and flatMap differ by exactly one level of nesting — confuse them and you get Optional<Optional<T>> or Stream<Stream<T>>. Optional makes absence explicit, but only for return types. Parallel streams offer speed almost for free — and just as easily break correctness in silence. And generics keep all of it type-safe at compile time, even though erasure removes them at runtime. The full map lives in the layers below.
Topic map
- Lambda Expressions — a concise single-method implementation; a closure over effectively-final variables;
thisis the enclosing instance. - Functional Interfaces — exactly one abstract method,
@FunctionalInterface, and the ready-madeFunction/Predicate/Supplier/Consumer. - The Stream API — a lazy pipeline: a source, intermediate ops, and a terminal op; single use; never mutates the source.
- map vs flatMap —
maptransforms an element,flatMapalso flattens one level of nesting. - The Optional Class — an explicit "the value may be absent" for return types;
map/orElse/ifPresentinstead of a blindget. - Parallel Streams — the common
ForkJoinPool, when it helps, and when it silently breaks correctness. - Generics — compile-time type safety, type erasure, bounds, and the
? extends/? superwildcards.
Common traps
| Mistake | Consequence |
|---|---|
| Capturing and then reassigning a local variable inside a lambda | Does not compile — a captured variable must be effectively final |
| Treating a stream's intermediate operations as immediate | Nothing runs until a terminal operation; the code seems "not to work" |
map over a function that returns Optional/Stream | Nesting like Optional<Optional<T>> — flatMap was what you needed |
A blind get() on an Optional with no check | NoSuchElementException — the same NullPointerException, merely deferred |
Using Optional for fields and parameters | Off-label: an extra wrapper, awkward for the caller |
Switching a stream to parallelStream "for speed" | Races when mutating shared state; wrong answer on a non-identity seed |
Expecting a generic type argument at runtime (instanceof List<String>) | Does not compile — erasure removed the element type |
Interview relevance
Modern Java comes up on almost every mid-level and up interview — but the check is your model, not the syntax. A candidate who says "a stream is lazy and a terminal operation triggers execution" and "generics are erased, they do not exist at runtime" immediately stands apart from one who memorised a list of methods.
Typical checks:
- That a lambda captures only effectively-final variables and that its
thisis the enclosing object, not the lambda. - Why a functional interface is exactly one abstract method and why that is what lets a lambda bind to it.
- A stream's laziness, the difference between intermediate and terminal operations, and single-use pipelines.
mapvsflatMap— exactly one level of nesting apart.- Why
Optionalexists and why it is for return types only. - When a parallel stream speeds things up and when it breaks correctness.
- Type erasure and its consequences (
new T(),instanceof List<String>, overload collisions).
Common wrong answer: "parallelStream is always faster". That opens a discussion of the overhead on small sources, the danger of blocking tasks in the shared ForkJoinPool, and how a non-associative accumulator or a non-identity seed silently yields a wrong result.