Skip to content

Commit 9102c06

Browse files
committed
Patterns
1 parent 78a88de commit 9102c06

File tree

7 files changed

+1617
-10
lines changed

7 files changed

+1617
-10
lines changed

TOPICS_PLAN.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,16 @@ Last resort: Raw threads, synchronized, wait/notify
123123

124124
### 6. pattern/ - Pattern Matching
125125

126-
| Status | Example | Concepts |
127-
|--------|--------------------------|----------------------------------------------|
128-
| [ ] | InstanceofPatternExample | Pattern matching for instanceof (Java 14+) |
129-
| [ ] | SwitchExpressionExample | Switch expressions, arrow syntax, yield |
130-
| [ ] | SwitchPatternExample | Pattern matching for switch (Java 17+) |
131-
| [ ] | RecordPatternExample | Deconstructing records in patterns (Java 21) |
132-
| [ ] | GuardedPatternExample | when clauses in switch patterns |
133-
| [ ] | ExhaustiveSwitchExample | Exhaustiveness with sealed types |
126+
All pattern matching features below are **finalized and production-ready** in Java 25.
127+
128+
| Status | Example | Concepts | Java Version |
129+
|--------|--------------------------|-----------------------------------------------------|--------------|
130+
| [x] | InstanceofPatternExample | Pattern matching for instanceof, flow scoping | Java 16+ |
131+
| [x] | SwitchExpressionExample | Switch expressions, arrow syntax, yield | Java 14+ |
132+
| [x] | SwitchPatternExample | Type patterns in switch, null handling, dominance | Java 21+ |
133+
| [x] | RecordPatternExample | Deconstructing records, nested patterns, var | Java 21+ |
134+
| [x] | GuardedPatternExample | when clauses, guard ordering, complex conditions | Java 21+ |
135+
| [x] | ExhaustiveSwitchExample | Sealed types + switch = compiler-verified coverage | Java 17/21+ |
134136

135137
---
136138

@@ -188,9 +190,9 @@ Last resort: Raw threads, synchronized, wait/notify
188190
| streams | Complete | 4/4 |
189191
| fp | Complete | 6/6 |
190192
| oop | Complete | 6/6 |
191-
| pattern | Not Started | 0/6 |
193+
| pattern | Complete | 6/6 |
192194
| strings | Not Started | 0/5 |
193195
| generics | Not Started | 0/5 |
194196
| exceptions | Not Started | 0/4 |
195197
| io | Not Started | 0/3 |
196-
| **Total** | | **40/63**|
198+
| **Total** | | **46/63**|
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package org.nkcoder.pattern;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Exhaustive Switch with Sealed Types (Java 17+/21+): Compiler-enforced completeness.
7+
*
8+
* <p><strong>Java 25 Status:</strong> Finalized and production-ready. Combining sealed types
9+
* with pattern matching enables the compiler to verify all cases are handled.
10+
*
11+
* <p>Key concepts:
12+
* <ul>
13+
* <li>Sealed types restrict which classes can implement/extend</li>
14+
* <li>Compiler knows all possible subtypes</li>
15+
* <li>Switch expressions must be exhaustive</li>
16+
* <li>No default needed when all cases covered</li>
17+
* </ul>
18+
*/
19+
public class ExhaustiveSwitchExample {
20+
21+
static void main(String[] args) {
22+
enumExhaustiveness();
23+
sealedClassExhaustiveness();
24+
sealedInterfaceExhaustiveness();
25+
nestedSealedTypes();
26+
algebraicDataTypes();
27+
benefitsOfExhaustiveness();
28+
}
29+
30+
// ===== Enum Exhaustiveness =====
31+
32+
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
33+
34+
static void enumExhaustiveness() {
35+
System.out.println("=== Enum Exhaustiveness ===");
36+
37+
Day day = Day.WED;
38+
39+
// All enum values covered - no default needed
40+
String type = switch (day) {
41+
case MON, TUE, WED, THU, FRI -> "Weekday";
42+
case SAT, SUN -> "Weekend";
43+
}; // Compiler verifies all values handled
44+
45+
System.out.println(" " + day + " is a " + type);
46+
47+
// If you add a new enum value, compiler will error on incomplete switches
48+
System.out.println("""
49+
50+
If you add 'HOLIDAY' to Day enum:
51+
- All switch expressions using Day will fail to compile
52+
- Forces you to handle the new case
53+
- No runtime surprises!
54+
""");
55+
}
56+
57+
// ===== Sealed Class Exhaustiveness =====
58+
59+
static sealed abstract class Shape permits Circle, Rectangle, Triangle {}
60+
61+
static final class Circle extends Shape {
62+
private final double radius;
63+
Circle(double radius) { this.radius = radius; }
64+
double radius() { return radius; }
65+
}
66+
67+
static final class Rectangle extends Shape {
68+
private final double width, height;
69+
Rectangle(double width, double height) { this.width = width; this.height = height; }
70+
double width() { return width; }
71+
double height() { return height; }
72+
}
73+
74+
static final class Triangle extends Shape {
75+
private final double base, height;
76+
Triangle(double base, double height) { this.base = base; this.height = height; }
77+
double base() { return base; }
78+
double height() { return height; }
79+
}
80+
81+
static void sealedClassExhaustiveness() {
82+
System.out.println("=== Sealed Class Exhaustiveness ===");
83+
84+
Shape shape = new Circle(5.0);
85+
86+
// All permitted subclasses covered - no default!
87+
double area = switch (shape) {
88+
case Circle c -> Math.PI * c.radius() * c.radius();
89+
case Rectangle r -> r.width() * r.height();
90+
case Triangle t -> 0.5 * t.base() * t.height();
91+
};
92+
93+
System.out.println(" Shape: " + shape.getClass().getSimpleName());
94+
System.out.println(" Area: " + String.format("%.2f", area));
95+
96+
// Test all shapes
97+
Shape[] shapes = { new Circle(3), new Rectangle(4, 5), new Triangle(6, 4) };
98+
for (Shape s : shapes) {
99+
String desc = switch (s) {
100+
case Circle c -> "Circle r=" + c.radius();
101+
case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
102+
case Triangle t -> "Triangle base=" + t.base() + " h=" + t.height();
103+
};
104+
System.out.println(" " + desc);
105+
}
106+
107+
System.out.println();
108+
}
109+
110+
// ===== Sealed Interface with Records =====
111+
112+
sealed interface Result<T> permits Success, Failure, Pending {}
113+
114+
record Success<T>(T value) implements Result<T> {}
115+
record Failure<T>(String error, Exception cause) implements Result<T> {}
116+
record Pending<T>() implements Result<T> {}
117+
118+
static void sealedInterfaceExhaustiveness() {
119+
System.out.println("=== Sealed Interface with Records ===");
120+
121+
Result<Integer> result = new Success<>(42);
122+
123+
// Exhaustive with record patterns
124+
String message = switch (result) {
125+
case Success<Integer>(Integer value) -> "Got value: " + value;
126+
case Failure<Integer>(String error, Exception cause) -> "Error: " + error;
127+
case Pending<Integer>() -> "Still processing...";
128+
};
129+
130+
System.out.println(" Result: " + message);
131+
132+
// Test all result types
133+
Result<String>[] results = new Result[] {
134+
new Success<>("Hello"),
135+
new Failure<>("Network error", new RuntimeException("Connection refused")),
136+
new Pending<>()
137+
};
138+
139+
for (Result<String> r : results) {
140+
String status = switch (r) {
141+
case Success(var v) -> "SUCCESS: " + v;
142+
case Failure(var err, var cause) -> "FAILED: " + err;
143+
case Pending() -> "PENDING";
144+
};
145+
System.out.println(" " + status);
146+
}
147+
148+
System.out.println();
149+
}
150+
151+
// ===== Nested Sealed Types =====
152+
153+
sealed interface Expr permits Literal, BinaryOp, UnaryOp {}
154+
155+
record Literal(double value) implements Expr {}
156+
157+
sealed interface BinaryOp extends Expr permits Add, Sub, Mul, Div {}
158+
record Add(Expr left, Expr right) implements BinaryOp {}
159+
record Sub(Expr left, Expr right) implements BinaryOp {}
160+
record Mul(Expr left, Expr right) implements BinaryOp {}
161+
record Div(Expr left, Expr right) implements BinaryOp {}
162+
163+
sealed interface UnaryOp extends Expr permits Neg, Abs {}
164+
record Neg(Expr operand) implements UnaryOp {}
165+
record Abs(Expr operand) implements UnaryOp {}
166+
167+
static void nestedSealedTypes() {
168+
System.out.println("=== Nested Sealed Types ===");
169+
170+
// Expression: abs(-(5 + 3) * 2)
171+
Expr expr = new Abs(new Mul(new Neg(new Add(new Literal(5), new Literal(3))), new Literal(2)));
172+
173+
double result = evaluate(expr);
174+
System.out.println(" Expression: abs(-(5 + 3) * 2)");
175+
System.out.println(" Result: " + result);
176+
177+
// Simpler expression: (10 - 4) / 2
178+
Expr simple = new Div(new Sub(new Literal(10), new Literal(4)), new Literal(2));
179+
System.out.println(" (10 - 4) / 2 = " + evaluate(simple));
180+
181+
System.out.println();
182+
}
183+
184+
static double evaluate(Expr expr) {
185+
return switch (expr) {
186+
case Literal(double v) -> v;
187+
case Add(var l, var r) -> evaluate(l) + evaluate(r);
188+
case Sub(var l, var r) -> evaluate(l) - evaluate(r);
189+
case Mul(var l, var r) -> evaluate(l) * evaluate(r);
190+
case Div(var l, var r) -> evaluate(l) / evaluate(r);
191+
case Neg(var e) -> -evaluate(e);
192+
case Abs(var e) -> Math.abs(evaluate(e));
193+
}; // All Expr subtypes covered!
194+
}
195+
196+
// ===== Algebraic Data Types =====
197+
198+
sealed interface Option<T> permits Some, None {}
199+
record Some<T>(T value) implements Option<T> {}
200+
record None<T>() implements Option<T> {}
201+
202+
sealed interface Either<L, R> permits Left, Right {}
203+
record Left<L, R>(L value) implements Either<L, R> {}
204+
record Right<L, R>(R value) implements Either<L, R> {}
205+
206+
static void algebraicDataTypes() {
207+
System.out.println("=== Algebraic Data Types ===");
208+
209+
// Option type (like Optional but pattern-matchable)
210+
Option<String> someValue = new Some<>("Hello");
211+
Option<String> noValue = new None<>();
212+
213+
for (Option<String> opt : List.of(someValue, noValue)) {
214+
String result = switch (opt) {
215+
case Some(String s) -> "Got: " + s;
216+
case None() -> "Nothing";
217+
};
218+
System.out.println(" Option: " + result);
219+
}
220+
221+
// Either type (error or success)
222+
Either<String, Integer> error = new Left<>("Not a number");
223+
Either<String, Integer> success = new Right<>(42);
224+
225+
for (Either<String, Integer> either : List.of(error, success)) {
226+
String result = switch (either) {
227+
case Left(String err) -> "Error: " + err;
228+
case Right(Integer val) -> "Value: " + val;
229+
};
230+
System.out.println(" Either: " + result);
231+
}
232+
233+
// Combining ADTs
234+
System.out.println("\n Combining ADTs (safe division):");
235+
for (int[] pair : new int[][]{{10, 2}, {10, 0}, {-6, 3}}) {
236+
Either<String, Integer> divResult = safeDivide(pair[0], pair[1]);
237+
String output = switch (divResult) {
238+
case Left(String err) -> pair[0] + "/" + pair[1] + " = Error: " + err;
239+
case Right(Integer val) -> pair[0] + "/" + pair[1] + " = " + val;
240+
};
241+
System.out.println(" " + output);
242+
}
243+
244+
System.out.println();
245+
}
246+
247+
static Either<String, Integer> safeDivide(int a, int b) {
248+
if (b == 0) return new Left<>("Division by zero");
249+
return new Right<>(a / b);
250+
}
251+
252+
static void benefitsOfExhaustiveness() {
253+
System.out.println("=== Benefits of Exhaustive Switches ===");
254+
255+
System.out.println("""
256+
Compile-Time Safety:
257+
- Compiler verifies ALL cases handled
258+
- No default = intentional coverage
259+
- Adding new subtype breaks compilation (good!)
260+
261+
Refactoring Confidence:
262+
- Rename a case? Compiler catches all usages
263+
- Remove a subtype? Compiler finds dead code
264+
- Add a subtype? Compiler shows where to update
265+
266+
Self-Documenting Code:
267+
- Switch clearly shows all possibilities
268+
- No hidden "else" behavior
269+
- Intent is explicit in the code
270+
271+
When to use sealed types:
272+
- Domain models with fixed variants
273+
- State machines
274+
- AST/expression trees
275+
- Result/Option types
276+
- Event hierarchies
277+
278+
Pattern:
279+
```
280+
sealed interface X permits A, B, C {}
281+
282+
// In switch - compiler enforces completeness:
283+
switch (x) {
284+
case A a -> handleA(a);
285+
case B b -> handleB(b);
286+
case C c -> handleC(c);
287+
// No default needed!
288+
}
289+
```
290+
""");
291+
}
292+
}

0 commit comments

Comments
 (0)