Modern Java
Write concise code with lambdas and streams, then apply records, sealed types, and pattern switches deliberately.
Why it matters
Start from the concept, not the syntax
Meet a concrete situation where this idea earns its place, then state the boundary it guards.
Modern Java is noticeably shorter than the Java of a decade ago. Lambdas compress behavior into expressions, streams declare transformations instead of loops, records collapse data classes to one line, and sealed types plus pattern switches make exhaustive checking compiler-enforced.
By the end of this chapter
Verbose anonymous classes and manual type checks make code longer and more error-prone than it needs to be.
- Lambdas and streams
- Records
- Sealed types and pattern switch
Mechanism
How Java expresses the concept
Walk the actual types, rules, and runtime behavior involved—the same behavior described in the Java Language Specification.
The mental model
A functional interface has exactly one abstract method and can be fulfilled by a lambda. `Runnable`, `Comparator`, and `Callable` are functional interfaces; lambdas replace anonymous classes for short behavior. A lambda captures variables only if they are effectively final.
Streams pipeline collections: `stream().filter(...).map(...).collect(...)` expresses what to compute, not how to loop. Use streams for transformation and clarity; fall back to a plain loop when the work has side effects or needs careful ordering, and avoid `parallelStream` unless you have measured a need.
A `record` is a concise, shallowly immutable data carrier: `record Point(int x, int y) {}` generates the fields, a constructor, accessors, `equals`, `hashCode`, and `toString`. Sealed types (`sealed interface`) restrict which classes may implement them. A Java 21 pattern switch can then be exhaustive when its cases cover the permitted type space; `null` and separate-compilation edge cases still require care.
Pattern matching for `instanceof` and the `switch` pattern form remove boilerplate casts and make type-based branching exhaustive and checked by the compiler. These features keep Java expressive without adding a separate runtime.
Concept map
Inspect the building blocks as a whole
Use the map as a structural reference; it shows the pieces and their relationships, not an execution sequence.
Core Java
Modern Java: lambdas, streams, and records
Java 8 and later added functional style and compact data types. Records, sealed types, and pattern switches make Java code shorter and safer.
- 01Functional interfaces
- 02Lambdas capture final
- 03Stream pipelines
- 04Record data carriers
- 05Sealed types
- 06Pattern switch
Implementation
Encode the smallest runnable example
Write and run the code, then observe the behavior you just predicted with a real Java program.
Runnable snippet path
Predict, run, and change each example
Stream a transformation
filter, map, and collect into a new list without a manual loop.
import java.util.*;
public class Names {
static List<String> upper(List<String> items) {
return items.stream()
.filter(s -> !s.isBlank())
.map(String::toUpperCase)
.toList();
}
}Remember
Use streams for clear transformations; keep side effects out of them.
Change one input, predict the result, then compile or test it before continuing.
Record and pattern switch
A record carries data; a sealed type lets the switch be exhaustive and checked.
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double s) implements Shape {}
class Area {
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square q -> q.s() * q.s();
};
}
}Remember
This pattern switch is exhaustive for the two permitted non-null types; add a deliberate `case null` or default when null is part of the input contract.
Change one input, predict the result, then compile or test it before continuing.
Watch out
Break it on purpose, then repair the right layer
Predict the symptom before running the failure case, and close with the completion checklist.
Deliberate failure
Break
Replace a missing case in a type switch with a silent default branch
Observe
A newly added subtype is silently handled wrong
Repair
Use sealed types so the compiler enforces exhaustive handling instead of trusting a catch-all default
Why it matters
Use sealed types so the compiler enforces exhaustive handling instead of trusting a catch-all default
Completion checklist
- Use lambdas and stream pipelines for transformation
- Model data with records
- Branch exhaustively over sealed types with a pattern switch
Go deeper
Official references
The primary sources behind this chapter. Every claim above stays consistent with them.