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.