Pattern Matching for switch (Preview) in Java

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

  1. Basic Pattern Matching
  2. Type Patterns
  3. Guarded Patterns
  4. Null Handling
  5. Exhaustiveness Checking
  6. Deconstruction Patterns
  7. Advanced Use Cases
  8. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper