Pattern Matching
Switch Pattern Matching
When handling multiple object types in event handlers, parsers, or command dispatchers, cascading if-else chains become unwieldy. Switch pattern matching lets you dispatch on types elegantly, with automatic casting and compiler-verified exhaustiveness.
Basic Type Patterns
Basic.java
Replay: real traced execution (multi-file project)
public class Basic {
public static void main(String[] args) {
Object obj = "hello";
switch (obj) {
case String s -> System.out.println("String: " + s.toUpperCase());
case Integer i -> System.out.println("Integer: " + i * 2);
case Double d -> System.out.println("Double: " + d / 2);
default -> System.out.println("Unknown type");
}
}
}
public class Basic {
public static void main(String[] args) {
Object obj = 42;
switch (obj) {
case String s -> System.out.println("String: " + s.toUpperCase());
case Integer i -> System.out.println("Integer: " + i * 2);
case Double d -> System.out.println("Double: " + d / 2);
default -> System.out.println("Unknown type");
}
}
}
public class Basic {
public static void main(String[] args) {
Object obj = 3.14;
switch (obj) {
case String s -> System.out.println("String: " + s.toUpperCase());
case Integer i -> System.out.println("Integer: " + i * 2);
case Double d -> System.out.println("Double: " + d / 2);
default -> System.out.println("Unknown type");
}
}
}
public class Basic {
public static void main(String[] args) {
Object obj = true;
switch (obj) {
case String s -> System.out.println("String: " + s.toUpperCase());
case Integer i -> System.out.println("Integer: " + i * 2);
case Double d -> System.out.println("Double: " + d / 2);
default -> System.out.println("Unknown type");
}
}
}
obj ← hello
1public class Basic {2 public static void main(String[] args) {3 Object obj→ hello = "hello"; //@obj="hello", 42, 3.14, true4 switch (obj) {
obj ← 42
1public class Basic {2 public static void main(String[] args) {3 Object obj→ 42 = 42;4 switch (obj) {
obj ← 3.14
1public class Basic {2 public static void main(String[] args) {3 Object obj→ 3.14 = 3.14;4 switch (obj) {
obj ← true
1public class Basic {2 public static void main(String[] args) {3 Object obj→ true = true;4 switch (obj) {
type_pattern
A type pattern in switch matches the runtime type and binds a pattern variable in one step.
Switch Expressions with Patterns
Expression.java
Replay: real traced execution (multi-file project)
public class Expression {
static String describe(Object obj) {
return switch (obj) {
case String s -> "String of length " + s.length();
case Integer i -> "Integer: " + i;
case Double d -> "Double: " + d;
case null -> "null value";
default -> "Unknown type: " + obj.getClass().getSimpleName();
};
}
public static void main(String[] args) {
System.out.println(describe("hello"));
System.out.println(describe(42));
System.out.println(describe(3.14));
System.out.println(describe(null));
System.out.println(describe(true));
}
}
public static void main(String[] args)
12public static void main(String[] args) {13 System.out.println(describe("hello"));14 System.out.println(describe(42));static String describe(Object obj)
pass 1 of 51public class Expression {2 static String describe(Object objhello) {3 return switch (obj) {4 case String s -> "String of length " + s.length();5 case Integer i -> "Integer: " + i;6 case Double d -> "Double: " + d;7 case null -> "null value";8 default -> "Unknown type: " + obj.getClass().getSimpleName();9 };10 }All 5 passes — pass 1 is the card above pass obj1 hello 2 42 3 3.14 4 null 5 true System.out.println(describe("hello"));
12public static void main(String[] args) {13 System.out.println(describe("hello"));14 System.out.println(describe(42));15 System.out.println(describe(3.14));outputString of length 5System.out.println(describe(42));
13System.out.println(describe("hello"));14System.out.println(describe(42));15System.out.println(describe(3.14));16System.out.println(describe(null));outputInteger: 42System.out.println(describe(3.14));
14System.out.println(describe(42));15System.out.println(describe(3.14));16System.out.println(describe(null));17System.out.println(describe(true));outputDouble: 3.14System.out.println(describe(null));
15 System.out.println(describe(3.14));16 System.out.println(describe(null));17 System.out.println(describe(true));18}outputnull valueSystem.out.println(describe(true));
16 System.out.println(describe(null));17 System.out.println(describe(true));18}outputUnknown type: Boolean
Benefits
- Type testing: match on types, not just constants
- Pattern variables: automatic casting in each case
- Null handling:
case nullis explicit - Exhaustiveness: compiler checks all cases are covered
Null Handling
Null.java
Replay: real traced execution (multi-file project)
public class Null {
static void process(Object obj) {
switch (obj) {
case null -> System.out.println("Got null");
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer: " + i);
default -> System.out.println("Other: " + obj);
}
}
public static void main(String[] args) {
process("test");
process(123);
process(null); // handled explicitly
}
}
public static void main(String[] args)
11public static void main(String[] args) {12 process("test");13 process(123);static void process(Object obj)
pass 1 of 31public class Null {2 static void process(Object objtest) {3 switch (obj) {All 3 passes — pass 1 is the card above pass obj1 test 2 123 3 null process("test");
11public static void main(String[] args) {12 process("test");13 process(123);14 process(null); // handled explicitlyprocess(123);
12 process("test");13 process(123);14 process(null); // handled explicitly15}process(null); // handled explicitly
13 process(123);14 process(null); // handled explicitly15}
null_case
The explicit `case null` pattern allows safe null handling without NullPointerException.
Case Ordering
Ordering.java
Replay: real traced execution (multi-file project)
public class Ordering {
static void check(Object obj) {
switch (obj) {
case String s when s.isEmpty() -> System.out.println("Empty string");
case String s -> System.out.println("Non-empty string: " + s);
case Integer i when i < 0 -> System.out.println("Negative");
case Integer i -> System.out.println("Non-negative: " + i);
case null -> System.out.println("null");
default -> System.out.println("Other");
}
}
public static void main(String[] args) {
check("");
check("hello");
check(-5);
check(10);
check(null);
}
}
public static void main(String[] args)
13public static void main(String[] args) {14 check("");15 check("hello");static void check(Object obj)
pass 1 of 51public class Ordering {2 static void check(Object obj(empty)) {3 switch (obj) {All 5 passes — pass 1 is the card above pass obj1 (empty) 2 hello 3 -5 4 10 5 null check("");
13public static void main(String[] args) {14 check("");15 check("hello");16 check(-5);check("hello");
14check("");15check("hello");16check(-5);17check(10);check(-5);
15check("hello");16check(-5);17check(10);18check(null);check(10);
16 check(-5);17 check(10);18 check(null);19}check(null);
17 check(10);18 check(null);19}
dominance
Case ordering matters - more specific patterns must come before more general ones to avoid dominance errors.
Exhaustiveness Checking
Exhaustiveness.java
Replay: real traced execution (multi-file project)
public class Exhaustiveness {
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
};
}
public static void main(String[] args) {
Shape circle = new Circle(5.0);
Shape rect = new Rectangle(4.0, 6.0);
System.out.println("Circle area: " + area(circle));
System.out.println("Rectangle area: " + area(rect));
}
}
circle ← Circle[radius=5.0], rect ← Rectangle[width=4.0, height=6.0]
13public static void main(String[] args) {14 Shape circle→ Circle[radius=5.0] = new Circle(5.0);15 Shape rect→ Rectangle[width=4.0, height=6.0] = new Rectangle(4.0, 6.0);1617 System.out.println("Circle area: " + area(circleCircle[radius=5.0]));18 System.out.println("Rectangle area: " + area(rect));static double area(Shape shape)
pass 1 of 26static double area(Shape shapeCircle[radius=5.0]) {7 return switch (shape) {8 case Circle c -> Math.PI * c.radius() * c.radius();9 case Rectangle r -> r.width() * r.height();10 };11}System.out.println("Circle area: " + area(circle));
17 System.out.println("Circle area: " + area(circleCircle[radius=5.0]));18 System.out.println("Rectangle area: " + area(rectRectangle[width=4.0, height=6.0]));19}outputCircle area: 78.53981633974483static double area(Shape shape)
pass 2 of 26static double area(Shape shapeRectangle[width=4.0, height=6.0]) {7 return switch (shape) {8 case Circle c -> Math.PI * c.radius() * c.radius();9 case Rectangle r -> r.width() * r.height();10 };11}System.out.println("Rectangle area: " + area(rect));
17 System.out.println("Circle area: " + area(circle));18 System.out.println("Rectangle area: " + area(rectRectangle[width=4.0, height=6.0]));19}outputRectangle area: 24.0
exhaustiveness
The compiler verifies all possible types are handled, requiring either complete coverage or a default case.
Exercise: Practical.java
Build a shape area calculator that handles Circle, Rectangle, and Triangle types using switch pattern matching