Modern Java Features
Records, sealed classes, pattern matching, Optional — Java 14-21
Introduction
In this lesson, you'll learn about modern java features in Java. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Python, you're familiar with records, sealed classes, pattern matching, optional — java 14-21.
Java has its own approach to records, sealed classes, pattern matching, optional — java 14-21, which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
// Java 16+: record — like Python @dataclass
public record Point(double x, double y) {}
// Auto-generates: constructor, getters, equals, hashCode, toString
Point p = new Point(1.0, 2.0);
System.out.println(p); // Point[x=1.0, y=2.0]
System.out.println(p.equals(new Point(1.0, 2.0))); // true
// Java 17+: sealed classes — restrict subclasses
public sealed interface Shape
permits Circle, Rectangle, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double w, double h) implements Shape {}
// Java 21: pattern matching in switch (full)
String describe(Shape shape) {
return switch (shape) {
case Circle c -> "circle r=" + c.radius();
case Rectangle r -> "rect " + r.w() + "x" + r.h();
}; // no default needed — sealed = exhaustive
}
// Java 16+: pattern matching instanceof
Object obj = "hello";
if (obj instanceof String s && s.length() > 3) {
System.out.println(s.toUpperCase());
}
// Optional<T> — explicit null safety
Optional<String> findUser(int id) {
return id == 1 ? Optional.of("Alice") : Optional.empty();
}
findUser(1)
.map(String::toUpperCase)
.ifPresent(System.out::println); // "ALICE"Comparing to Python
Here's how you might have written similar code in Python:
from dataclasses import dataclass
from typing import Optional
# Dataclass (Python 3.7+)
@dataclass
class Point:
x: float
y: float
label: str = "origin"
p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0, label='origin')
p2 = Point(1.0, 2.0)
print(p == p2) # True — structural equality
# match/case (Python 3.10+)
def describe(shape):
match shape:
case {"type": "circle", "radius": r}:
return f"circle r={r}"
case {"type": "rect", "w": w, "h": h}:
return f"rect {w}x{h}"
case _:
return "unknown"
# Optional with None
def find_user(id: int) -> Optional[str]:
return "Alice" if id == 1 else None
user = find_user(1)
if user is not None:
print(user.upper())You may be used to different syntax or behavior.
record replaces @dataclass — both give constructor, equals, toString automatically
You may be used to different syntax or behavior.
sealed interface/class restricts which classes can implement/extend it
You may be used to different syntax or behavior.
Pattern matching in switch (Java 21) matches record types by structure
You may be used to different syntax or behavior.
Optional<T> makes null-returning functions explicit (vs Python's None)
You may be used to different syntax or behavior.
'instanceof String s' binds matched variable — no explicit cast needed
Step-by-Step Breakdown
1. Records
A Java record is a concise immutable class. It auto-generates a canonical constructor, getters (without 'get' prefix), equals, hashCode, and toString.
@dataclass
class Point:
x: float
y: floatrecord Point(double x, double y) {}
// Use: new Point(1.0, 2.0)
// Getters: p.x(), p.y() (no 'get' prefix)2. Sealed Classes
Sealed classes/interfaces restrict which classes can extend them. This enables exhaustive pattern matching — the compiler knows all subtypes.
# Python: just use ABCs or type hints — no enforcementsealed interface Expr permits Num, Add, Mul {}
record Num(int value) implements Expr {}
record Add(Expr l, Expr r) implements Expr {}
record Mul(Expr l, Expr r) implements Expr {}3. Pattern Matching Switch
Java 21 switch can match on types and deconstruct records. With sealed types, the compiler checks exhaustiveness.
match shape:
case Circle(r=r): return f"r={r}"
case Rect(w=w, h=h): return f"{w}x{h}"int eval(Expr e) {
return switch (e) {
case Num(var v) -> v;
case Add(var l, var r) -> eval(l) + eval(r);
case Mul(var l, var r) -> eval(l) * eval(r);
};
}4. Optional
Optional<T> explicitly models the possible absence of a value. Use map/flatMap/orElse for chaining without null checks.
def find(id) -> Optional[User]:
...
if user := find(id):
print(user.name)Optional<User> find(int id) { ... }
find(id)
.map(User::name)
.orElse("anonymous");Common Mistakes
When coming from Python, developers often make these mistakes:
- record replaces @dataclass — both give constructor, equals, toString automatically
- sealed interface/class restricts which classes can implement/extend it
- Pattern matching in switch (Java 21) matches record types by structure
Key Takeaways
- record = @dataclass — auto-generates constructor, getters, equals, toString
- sealed restricts subclasses; enables exhaustive switch pattern matching
- Pattern matching switch (Java 21) deconstructs records like Python match/case
- Optional<T> makes nullable returns explicit; chain with map/orElse instead of null checks