Introduction
Pattern Matching for switch is a powerful feature introduced as a preview in Java 17 and refined in subsequent versions. It enhances the traditional switch statement by allowing patterns in case labels, making code more concise, readable, and less error-prone.
Table of Contents
- Basic Pattern Matching
- Type Patterns
- Guarded Patterns
- Null Handling
- Exhaustiveness Checking
- Deconstruction Patterns
- Advanced Use Cases
- Best Practices
Basic Pattern Matching
Traditional vs Pattern Matching Switch
// Traditional approach with instanceof and casting public class BasicPatternMatching { // OLD WAY: Verbose and repetitive public static String processObjectOld(Object obj) { if (obj instanceof String) { String s = (String) obj; return "String: " + s.toUpperCase(); } else if (obj instanceof Integer) { Integer i = (Integer) obj; return "Integer: " + (i * 2); } else if (obj instanceof Double) { Double d = (Double) obj; return "Double: " + (d / 2); } else { return "Unknown type"; } } // NEW WAY: Clean and concise with pattern matching public static String processObjectNew(Object obj) { return switch (obj) { case String s -> "String: " + s.toUpperCase(); case Integer i -> "Integer: " + (i * 2); case Double d -> "Double: " + (d / 2); default -> "Unknown type"; }; } public static void main(String[] args) { System.out.println(processObjectOld("hello")); // String: HELLO System.out.println(processObjectNew("hello")); // String: HELLO System.out.println(processObjectOld(42)); // Integer: 84 System.out.println(processObjectNew(42)); // Integer: 84 System.out.println(processObjectOld(10.5)); // Double: 5.25 System.out.println(processObjectNew(10.5)); // Double: 5.25 } } Basic Syntax and Rules
public class SwitchSyntax { // Pattern matching with different return types public static void demonstrateBasicSyntax() { Object[] objects = {"Hello", 42, 3.14, true, null}; for (Object obj : objects) { String result = switch (obj) { case String s -> { System.out.println("Processing string: " + s); yield "String length: " + s.length(); } case Integer i -> "Integer value squared: " + (i * i); case Double d -> "Double half value: " + (d / 2); case Boolean b -> "Boolean negation: " + !b; case null -> "Received null object"; default -> "Unhandled type: " + obj.getClass().getSimpleName(); }; System.out.println(result); } } // Using traditional colon syntax with yield public static String traditionalSyntax(Object obj) { return switch (obj) { case String s: yield "String: " + s; case Integer i: yield "Integer: " + i; case Double d: yield "Double: " + d; default: yield "Unknown: " + obj; }; } public static void main(String[] args) { demonstrateBasicSyntax(); } } Type Patterns
Comprehensive Type Pattern Examples
import java.util.List; import java.util.Map; public class TypePatterns { // Handling collections and arrays public static String processCollection(Object collection) { return switch (collection) { case List<?> list -> "List with " + list.size() + " elements"; case Map<?, ?> map -> "Map with " + map.size() + " entries"; case String[] array -> "String array length: " + array.length; case int[] array -> "int array length: " + array.length; case null -> "Null collection"; default -> "Unknown collection type: " + collection.getClass().getSimpleName(); }; } // Working with Number hierarchy public static String processNumber(Number number) { return switch (number) { case Integer i -> "Integer: " + i; case Long l -> "Long: " + l; case Double d -> "Double: " + d; case Float f -> "Float: " + f; case Byte b -> "Byte: " + b; case Short s -> "Short: " + s; default -> "Unknown number type: " + number.getClass().getSimpleName(); }; } // Nested type patterns public static String processNested(Object obj) { return switch (obj) { case List<?> list when !list.isEmpty() -> { Object first = list.get(0); yield "Non-empty list, first element: " + switch (first) { case String s -> "String: " + s; case Integer i -> "Integer: " + i; default -> "Unknown: " + first; }; } case List<?> list -> "Empty list"; default -> "Not a list"; }; } // Complex type hierarchy example static abstract class Shape {} static class Circle extends Shape { private final double radius; Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } } static class Rectangle extends Shape { private final double width, height; Rectangle(double width, double height) { this.width = width; this.height = height; } public double getWidth() { return width; } public double getHeight() { return height; } } static class Triangle extends Shape { private final double base, height; Triangle(double base, double height) { this.base = base; this.height = height; } public double getBase() { return base; } public double getHeight() { return height; } } public static double calculateArea(Shape shape) { return switch (shape) { case Circle c -> Math.PI * c.getRadius() * c.getRadius(); case Rectangle r -> r.getWidth() * r.getHeight(); case Triangle t -> 0.5 * t.getBase() * t.getHeight(); case null -> throw new IllegalArgumentException("Shape cannot be null"); }; } public static void main(String[] args) { // Test collection processing System.out.println(processCollection(List.of(1, 2, 3))); System.out.println(processCollection(Map.of("a", 1, "b", 2))); System.out.println(processCollection(new String[]{"a", "b", "c"})); // Test number processing System.out.println(processNumber(42)); System.out.println(processNumber(3.14)); System.out.println(processNumber(100L)); // Test shape area calculation System.out.println("Circle area: " + calculateArea(new Circle(5.0))); System.out.println("Rectangle area: " + calculateArea(new Rectangle(4.0, 6.0))); System.out.println("Triangle area: " + calculateArea(new Triangle(3.0, 4.0))); } } Guarded Patterns
Using When Clauses for Additional Conditions
import java.time.LocalDate; import java.util.Arrays; public class GuardedPatterns { // Basic guarded patterns with conditions public static String describeNumber(Number number) { return switch (number) { case Integer i when i > 0 -> "Positive integer: " + i; case Integer i when i < 0 -> "Negative integer: " + i; case Integer i -> "Zero"; case Double d when d > 100.0 -> "Large double: " + d; case Double d when d < 0.0 -> "Negative double: " + d; case Double d -> "Small double: " + d; default -> "Other number: " + number; }; } // String patterns with guards public static String processString(String str) { return switch (str) { case String s when s.isEmpty() -> "Empty string"; case String s when s.length() > 10 -> "Long string: " + s.substring(0, 10) + "..."; case String s when s.equals(s.toUpperCase()) -> "Uppercase string: " + s; case String s when s.equals(s.toLowerCase()) -> "Lowercase string: " + s; case String s -> "Mixed case string: " + s; case null -> "Null string"; }; } // Complex conditions with multiple guards public static String analyzeObject(Object obj) { return switch (obj) { case String s when s.contains("@") && s.length() > 5 -> "Potential email: " + s; case List<?> list when list.size() > 10 -> "Large list with " + list.size() + " elements"; case List<?> list when list.isEmpty() -> "Empty list"; case int[] array when array.length == 0 -> "Empty int array"; case int[] array when Arrays.stream(array).anyMatch(n -> n < 0) -> "Int array with negative numbers"; case LocalDate date when date.isAfter(LocalDate.now()) -> "Future date: " + date; case LocalDate date when date.isBefore(LocalDate.now()) -> "Past date: " + date; case LocalDate date -> "Today's date: " + date; default -> "Unanalyzed object: " + obj; }; } // Combining type patterns and guards public static String processCollectionWithGuard(Object collection) { return switch (collection) { case List<?> list when list.size() > 5 -> "Large list: " + list.size() + " elements"; case List<?> list when ((List<?>) list).stream() .allMatch(e -> e instanceof String) -> "List of strings"; case Map<?, ?> map when map.containsKey("key") -> "Map contains 'key'"; case String[] array when array.length == 0 -> "Empty string array"; case String[] array when Arrays.stream(array) .allMatch(s -> s.length() > 3) -> "String array with long elements"; default -> "Other collection"; }; } // Nested guards public static String complexAnalysis(Object obj1, Object obj2) { return switch (obj1) { case String s1 when s1.length() > 5 -> switch (obj2) { case String s2 when s2.length() > 5 -> "Both are long strings"; case String s2 -> "First is long string, second is short string"; default -> "First is long string, second is not string"; }; case Integer i when i > 100 -> switch (obj2) { case Integer j when j > 100 -> "Both are large integers"; case Integer j -> "First is large integer, second is small"; default -> "First is large integer, second is not integer"; }; default -> "Other combination"; }; } public static void main(String[] args) { // Test number description System.out.println(describeNumber(42)); System.out.println(describeNumber(-5)); System.out.println(describeNumber(0)); System.out.println(describeNumber(150.0)); System.out.println(describeNumber(-10.5)); // Test string processing System.out.println(processString("")); System.out.println(processString("VERYLONGSTRINGHERE")); System.out.println(processString("UPPERCASE")); System.out.println(processString("lowercase")); System.out.println(processString("MixedCase")); // Test complex analysis System.out.println(complexAnalysis("hello world", "short")); System.out.println(complexAnalysis(200, 150)); System.out.println(complexAnalysis(200, "test")); } } Null Handling
Comprehensive Null Safety with Pattern Matching
import java.util.Optional; public class NullHandling { // Traditional null handling public static String traditionalNullHandling(String str) { if (str == null) { return "Null string"; } else if (str.isEmpty()) { return "Empty string"; } else { return "String: " + str; } } // Modern null handling with pattern matching public static String modernNullHandling(String str) { return switch (str) { case null -> "Null string"; case String s when s.isEmpty() -> "Empty string"; case String s -> "String: " + s; }; } // Null handling with multiple types public static String processWithNullSafety(Object obj) { return switch (obj) { case null -> "Received null object"; case String s -> "String: " + s; case Integer i -> "Integer: " + i; case Double d -> "Double: " + d; default -> "Other: " + obj; }; } // Optional integration with pattern matching public static String processOptional(Optional<?> optional) { return switch (optional) { case Optional<?> opt when opt.isEmpty() -> "Empty optional"; case Optional<String> opt -> "String optional: " + opt.get(); case Optional<Integer> opt -> "Integer optional: " + opt.get(); case Optional<Double> opt -> "Double optional: " + opt.get(); default -> "Other optional"; }; } // Complex null handling scenarios public static String complexNullHandling(Object[] objects) { return switch (objects) { case null -> "Null array"; case Object[] array when array.length == 0 -> "Empty array"; case Object[] array -> { StringBuilder result = new StringBuilder("Array with elements: "); for (Object obj : array) { String elementDesc = switch (obj) { case null -> "null"; case String s -> "'" + s + "'"; case Integer i -> i.toString(); default -> obj.toString(); }; result.append(elementDesc).append(", "); } yield result.toString(); } }; } // Null-safe method chaining with pattern matching static class User { private final String name; private final Address address; User(String name, Address address) { this.name = name; this.address = address; } public String getName() { return name; } public Address getAddress() { return address; } } static class Address { private final String city; private final String zipCode; Address(String city, String zipCode) { this.city = city; this.zipCode = zipCode; } public String getCity() { return city; } public String getZipCode() { return zipCode; } } public static String getUserCity(User user) { return switch (user) { case null -> "No user"; case User u when u.getAddress() == null -> "User has no address"; case User u -> "User city: " + u.getAddress().getCity(); }; } public static String getUserInfo(User user) { return switch (user) { case null -> "No user provided"; case User u when u.getName() == null -> "User with no name"; case User u when u.getAddress() == null -> "User '" + u.getName() + "' has no address"; case User u when u.getAddress().getCity() == null -> "User '" + u.getName() + "' has address with no city"; case User u -> "User '" + u.getName() + "' from '" + u.getAddress().getCity() + "'"; }; } public static void main(String[] args) { // Test basic null handling System.out.println(modernNullHandling(null)); System.out.println(modernNullHandling("")); System.out.println(modernNullHandling("Hello")); // Test array null handling System.out.println(complexNullHandling(null)); System.out.println(complexNullHandling(new Object[]{})); System.out.println(complexNullHandling(new Object[]{"test", 42, null})); // Test user scenarios System.out.println(getUserCity(null)); System.out.println(getUserCity(new User("John", null))); System.out.println(getUserCity(new User("Alice", new Address("New York", "10001")))); System.out.println(getUserInfo(null)); System.out.println(getUserInfo(new User(null, null))); System.out.println(getUserInfo(new User("Bob", null))); System.out.println(getUserInfo(new User("Carol", new Address(null, "20002")))); System.out.println(getUserInfo(new User("Dave", new Address("Boston", "02101")))); } } Exhaustiveness Checking
Ensuring Complete Pattern Coverage
// Sealed classes work perfectly with pattern matching exhaustiveness public class ExhaustivenessChecking { // Sealed hierarchy for complete pattern matching sealed interface Shape permits Circle, Rectangle, Triangle { double area(); } static final class Circle implements Shape { private final double radius; Circle(double radius) { this.radius = radius; } public double area() { return Math.PI * radius * radius; } public double getRadius() { return radius; } } static final class Rectangle implements Shape { private final double width, height; Rectangle(double width, double height) { this.width = width; this.height = height; } public double area() { return width * height; } public double getWidth() { return width; } public double getHeight() { return height; } } static final class Triangle implements Shape { private final double base, height; Triangle(double base, double height) { this.base = base; this.height = height; } public double area() { return 0.5 * base * height; } public double getBase() { return base; } public double getHeight() { return height; } } // Exhaustive switch - compiler knows all cases are covered public static String describeShape(Shape shape) { return switch (shape) { case Circle c -> "Circle with radius " + c.getRadius(); case Rectangle r -> "Rectangle " + r.getWidth() + "x" + r.getHeight(); case Triangle t -> "Triangle base=" + t.getBase() + ", height=" + t.getHeight(); // No default needed - all permitted types are covered }; } // Non-sealed example requiring default static class Animal {} static class Dog extends Animal { public String bark() { return "Woof!"; } } static class Cat extends Animal { public String meow() { return "Meow!"; } } public static String handleAnimal(Animal animal) { return switch (animal) { case Dog d -> "Dog says: " + d.bark(); case Cat c -> "Cat says: " + c.meow(); case null -> "No animal"; default -> "Unknown animal"; // Required because Animal is not sealed }; } // Enum exhaustive pattern matching enum Color { RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA } public static String describeColor(Color color) { return switch (color) { case RED -> "Primary color - Red"; case GREEN -> "Primary color - Green"; case BLUE -> "Primary color - Blue"; case YELLOW -> "Secondary color - Yellow"; case CYAN -> "Secondary color - Cyan"; case MAGENTA -> "Secondary color - Magenta"; // No default needed - all enum values are covered }; } // Using guards while maintaining exhaustiveness public static String analyzeNumber(Number number) { return switch (number) { case Integer i when i > 0 -> "Positive integer"; case Integer i when i < 0 -> "Negative integer"; case Integer i -> "Zero"; // Covers remaining Integer cases case Double d when d > 0 -> "Positive double"; case Double d when d < 0 -> "Negative double"; case Double d -> "Zero double"; // Covers remaining Double cases case Long l -> "Long number"; // Covers all Long cases case Float f -> "Float number"; // Covers all Float cases case null -> "Null number"; default -> "Other number type"; // Covers Byte, Short, etc. }; } // Complex exhaustive pattern with nested switches public static String processNestedExhaustive(Object obj1, Object obj2) { return switch (obj1) { case String s1 -> switch (obj2) { case String s2 -> "Both strings: " + s1 + " and " + s2; case Integer i -> "String and Integer: " + s1 + ", " + i; case null -> "String and null: " + s1; default -> "String and other: " + s1; }; case Integer i1 -> switch (obj2) { case String s -> "Integer and String: " + i1 + ", " + s; case Integer i2 -> "Both integers: " + i1 + " and " + i2; case null -> "Integer and null: " + i1; default -> "Integer and other: " + i1; }; case null -> switch (obj2) { case String s -> "Null and String: " + s; case Integer i -> "Null and Integer: " + i; case null -> "Both null"; default -> "Null and other"; }; default -> switch (obj2) { case String s -> "Other and String: " + s; case Integer i -> "Other and Integer: " + i; case null -> "Other and null"; default -> "Both other types"; }; }; } public static void main(String[] args) { // Test sealed hierarchy Shape circle = new Circle(5.0); Shape rectangle = new Rectangle(4.0, 6.0); Shape triangle = new Triangle(3.0, 4.0); System.out.println(describeShape(circle)); System.out.println(describeShape(rectangle)); System.out.println(describeShape(triangle)); // Test enum exhaustive matching for (Color color : Color.values()) { System.out.println(describeColor(color)); } // Test nested exhaustive patterns System.out.println(processNestedExhaustive("hello", "world")); System.out.println(processNestedExhaustive(42, "test")); System.out.println(processNestedExhaustive(null, 100)); System.out.println(processNestedExhaustive(null, null)); } } Deconstruction Patterns
Record Patterns and Deconstruction
import java.util.List; public class DeconstructionPatterns { // Basic record deconstruction record Point(int x, int y) {} record Circle(Point center, double radius) {} record Rectangle(Point topLeft, Point bottomRight) {} public static String processShape(Object shape) { return switch (shape) { case Point(int x, int y) -> "Point at (" + x + ", " + y + ")"; case Circle(Point center, double radius) -> "Circle at (" + center.x() + ", " + center.y() + ") with radius " + radius; case Rectangle(Point tl, Point br) -> "Rectangle from (" + tl.x() + ", " + tl.y() + ") to (" + br.x() + ", " + br.y() + ")"; case null -> "Null shape"; default -> "Unknown shape"; }; } // Nested record deconstruction record Person(String name, int age, Address address) {} record Address(String street, String city, String zipCode) {} public static String describePerson(Person person) { return switch (person) { case Person(String name, int age, Address(String street, String city, String zip)) when age < 18 -> "Minor " + name + " from " + city; case Person(String name, int age, Address(String street, String city, String zip)) when age >= 65 -> "Senior " + name + " from " + city; case Person(String name, int age, Address(String street, String city, String zip)) -> "Adult " + name + " from " + city; case null -> "No person"; }; } // Deconstruction with guards public static String analyzePoint(Point p) { return switch (p) { case Point(int x, int y) when x == 0 && y == 0 -> "Origin point"; case Point(int x, int y) when x == y -> "Point on diagonal: (" + x + ", " + y + ")"; case Point(int x, int y) when x > 0 && y > 0 -> "Point in first quadrant"; case Point(int x, int y) when x < 0 && y > 0 -> "Point in second quadrant"; case Point(int x, int y) when x < 0 && y < 0 -> "Point in third quadrant"; case Point(int x, int y) when x > 0 && y < 0 -> "Point in fourth quadrant"; case Point(int x, int y) -> "Point on axis: (" + x + ", " + y + ")"; }; } // Array deconstruction patterns public static String processArray(Object array) { return switch (array) { case int[] arr when arr.length == 0 -> "Empty int array"; case int[] { int first, int... rest } -> "Int array starting with " + first + ", remaining " + rest.length + " elements"; case String[] arr when arr.length == 0 -> "Empty string array"; case String[] { String first, String second, String... rest } -> "String array: first='" + first + "', second='" + second + "', remaining: " + rest.length; case null -> "Null array"; default -> "Other array type"; }; } // Complex deconstruction with collections record Order(String id, List<OrderItem> items, Customer customer) {} record OrderItem(String productId, int quantity, double price) {} record Customer(String name, String email, LoyaltyStatus status) {} enum LoyaltyStatus { REGULAR, SILVER, GOLD, PLATINUM } public static String processOrder(Order order) { return switch (order) { case Order(String id, List<OrderItem> items, Customer customer) when items.isEmpty() -> "Empty order " + id + " for customer " + customer.name(); case Order(String id, List<OrderItem> items, Customer(String name, String email, LoyaltyStatus status)) when status == LoyaltyStatus.PLATINUM -> { double total = items.stream() .mapToDouble(item -> item.quantity() * item.price()) .sum(); yield "Platinum customer " + name + " - Order " + id + " total: $" + total; } case Order(String id, List<OrderItem> items, Customer customer) -> { int totalItems = items.stream() .mapToInt(OrderItem::quantity) .sum(); yield "Order " + id + " for " + customer.name() + " with " + totalItems + " total items"; } case null -> "Null order"; }; } // Deconstruction in method parameters (Java 21+ preview) public static double calculateArea(Object shape) { return switch (shape) { case Point(int x, int y) -> 0.0; // Points have no area case Circle(Point center, double radius) -> Math.PI * radius * radius; case Rectangle(Point tl, Point br) -> Math.abs((br.x() - tl.x()) * (br.y() - tl.y())); default -> throw new IllegalArgumentException("Unknown shape: " + shape); }; } public static void main(String[] args) { // Test basic record deconstruction Point p = new Point(3, 4); Circle c = new Circle(new Point(1, 1), 5.0); Rectangle r = new Rectangle(new Point(0, 0), new Point(10, 10)); System.out.println(processShape(p)); System.out.println(processShape(c)); System.out.println(processShape(r)); // Test point analysis System.out.println(analyzePoint(new Point(0, 0))); System.out.println(analyzePoint(new Point(5, 5))); System.out.println(analyzePoint(new Point(3, 4))); System.out.println(analyzePoint(new Point(-2, 3))); // Test array deconstruction System.out.println(processArray(new int[]{1, 2, 3, 4, 5})); System.out.println(processArray(new String[]{"a", "b", "c"})); // Test complex order processing Customer customer = new Customer("John Doe", "[email protected]", LoyaltyStatus.PLATINUM); List<OrderItem> items = List.of( new OrderItem("prod1", 2, 25.0), new OrderItem("prod2", 1, 50.0) ); Order order = new Order("order123", items, customer); System.out.println(processOrder(order)); System.out.println(processOrder(new Order("empty", List.of(), customer))); } } Advanced Use Cases
Real-World Application Examples
import java.time.LocalDateTime; import java.util.*; import java.util.function.Function; public class AdvancedUseCases { // API Response handling sealed interface ApiResponse<T> permits Success, Error, Loading { default boolean isSuccess() { return this instanceof Success; } default boolean isError() { return this instanceof Error; } default boolean isLoading() { return this instanceof Loading; } } record Success<T>(T data, String message) implements ApiResponse<T> {} record Error<T>(String errorCode, String errorMessage, LocalDateTime timestamp) implements ApiResponse<T> {} record Loading<T>(String requestId, LocalDateTime startedAt) implements ApiResponse<T> {} public static <T> String handleApiResponse(ApiResponse<T> response) { return switch (response) { case Success<T>(T data, String msg) -> "Success: " + msg + " - Data: " + data; case Error<T>(String code, String msg, LocalDateTime time) -> "Error [" + code + "]: " + msg + " at " + time; case Loading<T>(String reqId, LocalDateTime start) -> "Loading request " + reqId + " started at " + start; }; } // JSON-like structure processing sealed interface JsonValue permits JsonObject, JsonArray, JsonString, JsonNumber, JsonBoolean, JsonNull {} record JsonObject(Map<String, JsonValue> properties) implements JsonValue {} record JsonArray(List<JsonValue> elements) implements JsonValue {} record JsonString(String value) implements JsonValue {} record JsonNumber(double value) implements JsonValue {} record JsonBoolean(boolean value) implements JsonValue {} record JsonNull() implements JsonValue {} public static String jsonToString(JsonValue json) { return switch (json) { case JsonObject(Map<String, JsonValue> props) -> { StringBuilder sb = new StringBuilder("{"); props.forEach((key, value) -> sb.append("\"").append(key).append("\": ") .append(jsonToString(value)).append(", ")); if (!props.isEmpty()) { sb.setLength(sb.length() - 2); // Remove trailing comma } sb.append("}"); yield sb.toString(); } case JsonArray(List<JsonValue> elements) -> { StringBuilder sb = new StringBuilder("["); elements.forEach(element -> sb.append(jsonToString(element)).append(", ")); if (!elements.isEmpty()) { sb.setLength(sb.length() - 2); } sb.append("]"); yield sb.toString(); } case JsonString(String value) -> "\"" + value + "\""; case JsonNumber(double value) -> String.valueOf(value); case JsonBoolean(boolean value) -> String.valueOf(value); case JsonNull() -> "null"; }; } // AST (Abstract Syntax Tree) processing sealed interface Expr permits Constant, Variable, Add, Multiply, Power { double evaluate(Map<String, Double> context); } record Constant(double value) implements Expr { public double evaluate(Map<String, Double> context) { return value; } } record Variable(String name) implements Expr { public double evaluate(Map<String, Double> context) { return context.getOrDefault(name, 0.0); } } record Add(Expr left, Expr right) implements Expr { public double evaluate(Map<String, Double> context) { return left.evaluate(context) + right.evaluate(context); } } record Multiply(Expr left, Expr right) implements Expr { public double evaluate(Map<String, Double> context) { return left.evaluate(context) * right.evaluate(context); } } record Power(Expr base, Expr exponent) implements Expr { public double evaluate(Map<String, Double> context) { return Math.pow(base.evaluate(context), exponent.evaluate(context)); } } public static String formatExpression(Expr expr) { return switch (expr) { case Constant(double value) -> String.valueOf(value); case Variable(String name) -> name; case Add(Expr left, Expr right) -> "(" + formatExpression(left) + " + " + formatExpression(right) + ")"; case Multiply(Expr left, Expr right) -> formatExpression(left) + " * " + formatExpression(right); case Power(Expr base, Expr exponent) -> formatExpression(base) + "^" + formatExpression(exponent); }; } public static Expr simplify(Expr expr) { return switch (expr) { case Add(Constant left, Constant right) -> new Constant(left.value() + right.value()); case Multiply(Constant left, Constant right) -> new Constant(left.value() * right.value()); case Add(Expr left, Constant c) when c.value() == 0 -> simplify(left); case Add(Constant c, Expr right) when c.value() == 0 -> simplify(right); case Multiply(Expr left, Constant c) when c.value() == 1 -> simplify(left); case Multiply(Constant c, Expr right) when c.value() == 1 -> simplify(right); case Multiply(Expr left, Constant c) when c.value() == 0 -> new Constant(0); case Multiply(Constant c, Expr right) when c.value() == 0 -> new Constant(0); case Add(Expr left, Expr right) -> new Add(simplify(left), simplify(right)); case Multiply(Expr left, Expr right) -> new Multiply(simplify(left), simplify(right)); case Power(Expr base, Expr exponent) -> new Power(simplify(base), simplify(exponent)); default -> expr; }; } // Domain-specific language processing sealed interface Command permits Login, Logout, Transfer, Query { String getUserId(); } record Login(String userId, String password) implements Command { public String getUserId() { return userId; } } record Logout(String userId) implements Command { public String getUserId() { return userId; } } record Transfer(String userId, String fromAccount, String toAccount, double amount) implements Command { public String getUserId() { return userId; } } record Query(String userId, String account, LocalDateTime from, LocalDateTime to) implements Command { public String getUserId() { return userId; } } public static String processCommand(Command command) { return switch (command) { case Login(String userId, String password) -> "Processing login for user: " + userId; case Logout(String userId) -> "Processing logout for user: " + userId; case Transfer(String userId, String from, String to, double amount) when amount > 10000 -> "Large transfer from " + from + " to " + to + ": $" + amount; case Transfer(String userId, String from, String to, double amount) -> "Transfer from " + from + " to " + to + ": $" + amount; case Query(String userId, String account, LocalDateTime from, LocalDateTime to) -> "Query for account " + account + " from " + from + " to " + to; }; } public static void main(String[] args) { // Test API response handling ApiResponse<String> success = new Success<>("Hello World", "Data retrieved"); ApiResponse<String> error = new Error<>("404", "Not found", LocalDateTime.now()); ApiResponse<String> loading = new Loading<>("req123", LocalDateTime.now()); System.out.println(handleApiResponse(success)); System.out.println(handleApiResponse(error)); System.out.println(handleApiResponse(loading)); // Test JSON processing JsonValue json = new JsonObject(Map.of( "name", new JsonString("John"), "age", new JsonNumber(30), "active", new JsonBoolean(true), "tags", new JsonArray(List.of( new JsonString("java"), new JsonString("programming") )) )); System.out.println(jsonToString(json)); // Test expression processing Expr complexExpr = new Add( new Multiply(new Constant(2), new Variable("x")), new Power(new Constant(3), new Constant(2)) ); System.out.println("Original: " + formatExpression(complexExpr)); System.out.println("Simplified: " + formatExpression(simplify(complexExpr))); Map<String, Double> context = Map.of("x", 5.0); System.out.println("Evaluated: " + complexExpr.evaluate(context)); // Test command processing Command transfer = new Transfer("user123", "acc1", "acc2", 15000.0); Command login = new Login("user123", "password"); System.out.println(processCommand(transfer)); System.out.println(processCommand(login)); } } Best Practices
Code Organization and Guidelines
import java.util.*; public class BestPractices { /* * BEST PRACTICES FOR PATTERN MATCHING IN SWITCH: * * 1. Use sealed hierarchies for exhaustive matching * 2. Prefer arrow syntax over colon syntax * 3. Use guards for complex conditions * 4. Handle null explicitly * 5. Maintain readability with proper formatting * 6. Use deconstruction patterns for records * 7. Avoid overly complex nested patterns */ // GOOD: Clear, readable pattern matching public static String goodPatternMatching(Object obj) { return switch (obj) { case String s when s.length() > 100 -> "Very long string"; case String s when s.isEmpty() -> "Empty string"; case String s -> "String: " + s; case Integer i when i > 0 -> "Positive integer"; case Integer i -> "Non-positive integer"; case List<?> list when list.size() > 10 -> "Large list"; case List<?> list -> "Small list"; case null -> "Null value"; default -> "Other type"; }; } // BAD: Overly complex and hard to read public static String badPatternMatching(Object obj) { return switch (obj) { case String s when s.length() > 100 && s.contains("special") && s.startsWith("prefix") -> "Complex string condition"; case Integer i when i > 0 && i < 100 && i % 2 == 0 && String.valueOf(i).length() > 2 -> "Overly specific integer"; default -> "Other"; }; } // GOOD: Using sealed classes for type safety sealed interface PaymentMethod permits CreditCard, PayPal, BankTransfer { String getAccountId(); } record CreditCard(String cardNumber, String expiry, String cvv) implements PaymentMethod { public String getAccountId() { return cardNumber; } } record PayPal(String email, String token) implements PaymentMethod { public String getAccountId() { return email; } } record BankTransfer(String accountNumber, String routingNumber) implements PaymentMethod { public String getAccountId() { return accountNumber; } } public static String processPayment(PaymentMethod payment) { return switch (payment) { case CreditCard(String card, String expiry, String cvv) -> "Processing credit card: " + card.substring(card.length() - 4); case PayPal(String email, String token) -> "Processing PayPal: " + email; case BankTransfer(String account, String routing) -> "Processing bank transfer: " + account; // No default needed - exhaustive due to sealed interface }; } // GOOD: Proper null handling public static String safeStringProcessing(String str) { return switch (str) { case null -> "Null string"; case String s when s.trim().isEmpty() -> "Empty or whitespace string"; case String s -> "Valid string: " + s; }; } // GOOD: Using pattern matching for validation public static ValidationResult validateUserInput(Object input) { return switch (input) { case String s when s.length() < 3 -> new ValidationResult(false, "Input too short"); case String s when s.length() > 50 -> new ValidationResult(false, "Input too long"); case String s when !s.matches("[a-zA-Z0-9 ]+") -> new ValidationResult(false, "Invalid characters"); case String s -> new ValidationResult(true, "Valid input"); case Integer i when i < 0 -> new ValidationResult(false, "Negative number not allowed"); case Integer i when i > 1000 -> new ValidationResult(false, "Number too large"); case Integer i -> new ValidationResult(true, "Valid number"); case null -> new ValidationResult(false, "Input cannot be null"); default -> new ValidationResult(false, "Unsupported input type"); }; } record ValidationResult(boolean isValid, String message) {} // GOOD: Complex business logic with clear structure public static String processBusinessRule(Object data, UserContext context) { return switch (data) { case Order order when context.hasPermission("PROCESS_ORDER") -> processOrder(order); case Customer customer when context.hasPermission("VIEW_CUSTOMER") -> "Customer: " + customer.name(); case ReportRequest request when context.hasPermission("GENERATE_REPORTS") -> generateReport(request); case null -> "No data provided"; default -> "Unauthorized or unsupported data type"; }; } // Helper methods for business logic private static String processOrder(Order order) { return "Order processed"; } private static String generateReport(ReportRequest request) { return "Report generated"; } // Supporting classes record Order(String id, double amount) {} record Customer(String name, String email) {} record ReportRequest(String type, LocalDateTime from, LocalDateTime to) {} static class UserContext { private final Set<String> permissions; UserContext(Set<String> permissions) { this.permissions = permissions; } boolean hasPermission(String permission) { return permissions.contains(permission); } } // GOOD: Pattern matching in streams public static List<String> processItems(List<Object> items) { return items.stream() .map(item -> switch (item) { case String s -> "String: " + s; case Integer i -> "Integer: " + i; case Double d -> "Double: " + d; case null -> "Null item"; default -> "Unknown: " + item.getClass().getSimpleName(); }) .toList(); } public static void main(String[] args) { // Test validation System.out.println(validateUserInput("hello")); System.out.println(validateUserInput("")); System.out.println(validateUserInput(42)); System.out.println(validateUserInput(-5)); System.out.println(validateUserInput(null)); // Test business logic UserContext adminContext = new UserContext(Set.of( "PROCESS_ORDER", "VIEW_CUSTOMER", "GENERATE_REPORTS")); System.out.println(processBusinessRule( new Order("123", 99.99), adminContext)); System.out.println(processBusinessRule( new Customer("John", "[email protected]"), adminContext)); // Test stream processing List<Object> mixedList = List.of("test", 42, 3.14, null, true); System.out.println(processItems(mixedList)); } } Performance Considerations
public class PerformanceConsiderations { /* * PERFORMANCE TIPS: * * 1. Pattern matching is generally optimized by the JVM * 2. Use the most specific patterns first * 3. Avoid unnecessary guards when possible * 4. Consider using traditional if-else for very simple checks * 5. Profile complex pattern matching in performance-critical code */ // Efficient ordering of patterns public static String efficientPatternOrdering(Object obj) { return switch (obj) { // Most common cases first case String s -> "String: " + s; case Integer i -> "Integer: " + i; // Less common cases later case Double d -> "Double: " + d; case List<?> list -> "List size: " + list.size(); // Rare cases last case Map<?, ?> map -> "Map size: " + map.size(); case null -> "Null"; default -> "Other"; }; } // Avoid unnecessary pattern complexity public static String simpleCase(Object obj) { // For very simple cases, traditional if might be clearer if (obj instanceof String s) { return "String: " + s; } else if (obj instanceof Integer i) { return "Integer: " + i; } else { return "Other"; } } } Pattern Matching for switch represents a significant evolution in Java's expressiveness, making code more readable, safer, and easier to maintain. By following these patterns and best practices, you can write more robust and maintainable Java code.