Pattern matching for instanceof is a powerful feature introduced in Java 16 (as a preview in Java 14) that simplifies type checking and casting, making code more readable and less error-prone.
1. Basic Pattern Matching with instanceof
Traditional vs Pattern Matching Approach
public class BasicPatternMatching {
public static void main(String[] args) {
demonstrateBasicPatternMatching();
demonstrateWithCollections();
demonstrateNullSafety();
}
public static void demonstrateBasicPatternMatching() {
System.out.println("=== Basic Pattern Matching ===");
Object obj = "Hello, Pattern Matching!";
// ❌ Traditional approach (verbose)
if (obj instanceof String) {
String str = (String) obj;
System.out.println("Traditional: " + str.toUpperCase());
}
// ✅ Pattern matching approach (concise)
if (obj instanceof String str) {
System.out.println("Pattern Matching: " + str.toUpperCase());
}
// More examples with different types
Object number = 42;
Object list = List.of(1, 2, 3);
processObject(number);
processObject(list);
processObject("Test String");
}
public static void processObject(Object obj) {
System.out.println("\nProcessing object: " + obj);
if (obj instanceof Integer i) {
System.out.println("It's an integer: " + (i * 2));
} else if (obj instanceof String s) {
System.out.println("It's a string: " + s.length() + " characters");
} else if (obj instanceof List<?> list) {
System.out.println("It's a list with " + list.size() + " elements");
} else {
System.out.println("Unknown type: " + obj.getClass().getSimpleName());
}
}
public static void demonstrateWithCollections() {
System.out.println("\n=== Pattern Matching with Collections ===");
List<Object> mixedList = List.of(
"String value",
42,
3.14,
List.of("a", "b", "c"),
new StringBuilder("builder")
);
for (Object item : mixedList) {
// Pattern matching in for-each loop
if (item instanceof String s) {
System.out.println("String: " + s.toUpperCase());
} else if (item instanceof Integer i) {
System.out.println("Integer: " + (i + 10));
} else if (item instanceof Double d) {
System.out.println("Double: " + (d * 2));
} else if (item instanceof List<?> sublist) {
System.out.println("List: " + sublist);
} else {
System.out.println("Other: " + item);
}
}
}
public static void demonstrateNullSafety() {
System.out.println("\n=== Null Safety with Pattern Matching ===");
Object possiblyNull = Math.random() > 0.5 ? "I'm not null" : null;
// Pattern matching safely handles null
if (possiblyNull instanceof String s) {
System.out.println("Not null string: " + s);
} else {
System.out.println("Object is null or not a String");
}
// Traditional approach would need explicit null check
if (possiblyNull != null && possiblyNull instanceof String) {
String s = (String) possiblyNull;
System.out.println("Traditional: " + s);
}
}
}
2. Advanced Pattern Matching Features
Pattern Variables and Scope
public class AdvancedPatternMatching {
public static void main(String[] args) {
demonstratePatternVariableScope();
demonstrateComplexConditions();
demonstrateNestedPatterns();
}
public static void demonstratePatternVariableScope() {
System.out.println("=== Pattern Variable Scope ===");
Object obj = "Scope Demonstration";
// Pattern variable 's' is only in scope within the if block
if (obj instanceof String s) {
System.out.println("Inside if block: " + s.toUpperCase());
// 's' is available here
}
// 's' is not available here - compilation error
// System.out.println(s);
// Pattern variable with logical operators
if (obj instanceof String s && s.length() > 5) {
System.out.println("Long string: " + s);
}
// This works because s is in scope for the entire && expression
if (obj instanceof String s && !s.isEmpty()) {
System.out.println("Non-empty string: " + s);
}
// This doesn't work - s is not in scope for the || expression
// if (obj instanceof String s || s.length() > 0) { } // Compilation error
}
public static void demonstrateComplexConditions() {
System.out.println("\n=== Complex Conditions ===");
Object[] objects = {
"Hello",
100,
"",
" ",
null,
"Another string"
};
for (Object obj : objects) {
// Complex condition with pattern matching
if (obj instanceof String s && s != null && !s.trim().isEmpty()) {
System.out.println("Valid non-empty string: '" + s + "'");
} else if (obj instanceof String s && (s == null || s.trim().isEmpty())) {
System.out.println("Invalid or empty string: " + (s == null ? "null" : "'" + s + "'"));
} else if (obj instanceof Integer i) {
System.out.println("Integer: " + i);
} else {
System.out.println("Other type: " + (obj == null ? "null" : obj.getClass().getSimpleName()));
}
}
}
public static void demonstrateNestedPatterns() {
System.out.println("\n=== Nested Patterns ===");
// Simulating complex object structures
Object complexObj = Map.of(
"user", Map.of("name", "John", "age", 30),
"settings", List.of("dark-mode", "notifications")
);
// Nested pattern matching
if (complexObj instanceof Map<?, ?> map &&
map.get("user") instanceof Map<?, ?> userMap &&
userMap.get("name") instanceof String userName) {
System.out.println("User name: " + userName);
}
// Processing nested collections
Object nestedData = List.of(
List.of(1, 2, 3),
"String item",
Map.of("key", "value")
);
if (nestedData instanceof List<?> list) {
for (Object item : list) {
if (item instanceof List<?> innerList) {
System.out.println("Nested list: " + innerList);
} else if (item instanceof String s) {
System.out.println("String item: " + s);
} else if (item instanceof Map<?, ?> map) {
System.out.println("Map item: " + map);
}
}
}
}
// Method with return values using pattern matching
public static String processWithReturn(Object obj) {
// Using pattern matching in return scenarios
if (obj instanceof String s) {
return "String: " + s.toUpperCase();
} else if (obj instanceof Integer i) {
return "Integer squared: " + (i * i);
} else if (obj instanceof List<?> list && !list.isEmpty()) {
return "Non-empty list with " + list.size() + " elements";
} else {
return "Unknown type: " + (obj == null ? "null" : obj.getClass().getSimpleName());
}
}
}
3. Real-World Use Cases
Data Processing and Validation
import java.util.*;
import java.time.*;
public class RealWorldPatternMatching {
public static void main(String[] args) {
demonstrateDataValidation();
demonstrateAPIParsing();
demonstrateEventProcessing();
}
public static void demonstrateDataValidation() {
System.out.println("=== Data Validation with Pattern Matching ===");
List<Object> userInputs = Arrays.asList(
"[email protected]",
"invalid-email",
25,
-5,
LocalDate.of(1990, 1, 1),
LocalDate.of(2025, 1, 1) // Future date
);
for (Object input : userInputs) {
ValidationResult result = validateUserInput(input);
System.out.println("Input: " + input + " -> " + result);
}
}
public static ValidationResult validateUserInput(Object input) {
// Email validation
if (input instanceof String s && isValidEmail(s)) {
return ValidationResult.valid("Email");
}
// Age validation
else if (input instanceof Integer age) {
if (age >= 0 && age <= 150) {
return ValidationResult.valid("Age: " + age);
} else {
return ValidationResult.invalid("Age must be between 0 and 150");
}
}
// Date validation
else if (input instanceof LocalDate date) {
if (!date.isAfter(LocalDate.now())) {
return ValidationResult.valid("Date: " + date);
} else {
return ValidationResult.invalid("Date cannot be in the future");
}
}
// Invalid type
else {
return ValidationResult.invalid("Unsupported input type");
}
}
private static boolean isValidEmail(String email) {
return email != null && email.contains("@") && email.contains(".");
}
static class ValidationResult {
private final boolean valid;
private final String message;
private ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
public static ValidationResult valid(String message) {
return new ValidationResult(true, message);
}
public static ValidationResult invalid(String message) {
return new ValidationResult(false, message);
}
@Override
public String toString() {
return (valid ? "VALID" : "INVALID") + ": " + message;
}
}
public static void demonstrateAPIParsing() {
System.out.println("\n=== API Response Parsing ===");
// Simulating different API responses
Object[] apiResponses = {
Map.of("status", "success", "data", Map.of("user", "John")),
Map.of("status", "error", "message", "Invalid request"),
"Unexpected response type",
404,
null
};
for (Object response : apiResponses) {
processAPIResponse(response);
}
}
public static void processAPIResponse(Object response) {
System.out.println("\nProcessing API response: " + response);
if (response instanceof Map<?, ?> map) {
// Success response
if (map.get("status") instanceof String status && "success".equals(status)) {
if (map.get("data") instanceof Map<?, ?> data) {
System.out.println("Success! Data: " + data);
} else {
System.out.println("Success but no data");
}
}
// Error response
else if (map.get("status") instanceof String status && "error".equals(status)) {
if (map.get("message") instanceof String message) {
System.out.println("Error: " + message);
} else {
System.out.println("Error but no message");
}
}
// Unknown status
else {
System.out.println("Unknown response status");
}
}
// HTTP status code
else if (response instanceof Integer code) {
if (code >= 200 && code < 300) {
System.out.println("HTTP Success: " + code);
} else if (code >= 400 && code < 500) {
System.out.println("HTTP Client Error: " + code);
} else if (code >= 500) {
System.out.println("HTTP Server Error: " + code);
} else {
System.out.println("HTTP Other: " + code);
}
}
// Unexpected response type
else if (response instanceof String s) {
System.out.println("Unexpected string response: " + s);
}
// Null response
else if (response == null) {
System.out.println("Null response received");
}
// Other types
else {
System.out.println("Unknown response type: " + response.getClass().getSimpleName());
}
}
public static void demonstrateEventProcessing() {
System.out.println("\n=== Event Processing ===");
List<Object> events = Arrays.asList(
new UserLoginEvent("john_doe", LocalDateTime.now()),
new PaymentEvent(100.50, "USD", "order_123"),
new SystemAlertEvent("HIGH", "CPU usage at 95%"),
"Invalid event string",
12345
);
for (Object event : events) {
processEvent(event);
}
}
public static void processEvent(Object event) {
if (event instanceof UserLoginEvent login && login.getUsername() != null) {
System.out.println("User login: " + login.getUsername() + " at " + login.getTimestamp());
} else if (event instanceof PaymentEvent payment) {
System.out.printf("Payment: %s %.2f for %s%n",
payment.getCurrency(), payment.getAmount(), payment.getOrderId());
} else if (event instanceof SystemAlertEvent alert && "HIGH".equals(alert.getSeverity())) {
System.out.println("HIGH ALERT: " + alert.getMessage());
} else if (event instanceof String s) {
System.out.println("Unexpected string event: " + s);
} else {
System.out.println("Unknown event type: " +
(event == null ? "null" : event.getClass().getSimpleName()));
}
}
}
// Event classes for demonstration
class UserLoginEvent {
private final String username;
private final LocalDateTime timestamp;
public UserLoginEvent(String username, LocalDateTime timestamp) {
this.username = username;
this.timestamp = timestamp;
}
public String getUsername() { return username; }
public LocalDateTime getTimestamp() { return timestamp; }
}
class PaymentEvent {
private final double amount;
private final String currency;
private final String orderId;
public PaymentEvent(double amount, String currency, String orderId) {
this.amount = amount;
this.currency = currency;
this.orderId = orderId;
}
public double getAmount() { return amount; }
public String getCurrency() { return currency; }
public String getOrderId() { return orderId; }
}
class SystemAlertEvent {
private final String severity;
private final String message;
public SystemAlertEvent(String severity, String message) {
this.severity = severity;
this.message = message;
}
public String getSeverity() { return severity; }
public String getMessage() { return message; }
}
4. Pattern Matching with Records and Sealed Classes
Records and Pattern Matching
public class RecordPatternMatching {
public static void main(String[] args) {
demonstrateRecordPatterns();
demonstrateSealedHierarchy();
}
// Record definitions
record Point(int x, int y) {}
record Circle(Point center, double radius) {}
record Rectangle(Point topLeft, Point bottomRight) {}
public static void demonstrateRecordPatterns() {
System.out.println("=== Pattern Matching with Records ===");
Object[] shapes = {
new Point(10, 20),
new Circle(new Point(5, 5), 10.0),
new Rectangle(new Point(0, 0), new Point(10, 10)),
"Not a shape"
};
for (Object shape : shapes) {
double area = calculateArea(shape);
System.out.println("Shape: " + shape + " -> Area: " + area);
}
}
public static double calculateArea(Object shape) {
// Pattern matching with records (Java 19+ preview)
if (shape instanceof Point p) {
return 0; // Point has no area
} else if (shape instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle r) {
int width = Math.abs(r.bottomRight().x() - r.topLeft().x());
int height = Math.abs(r.bottomRight().y() - r.topLeft().y());
return width * height;
} else {
return -1; // Unknown shape
}
}
// Enhanced pattern matching with record decomposition (Java 21+ preview)
public static void processShape(Object shape) {
// Record patterns - decomposing records in patterns
if (shape instanceof Point(int x, int y)) {
System.out.println("Point at (" + x + ", " + y + ")");
} else if (shape instanceof Circle(Point center, double radius)) {
System.out.println("Circle with center " + center + " and radius " + radius);
} else if (shape instanceof Rectangle(Point tl, Point br)) {
System.out.println("Rectangle from " + tl + " to " + br);
}
}
}
// Sealed classes for exhaustive pattern matching
sealed interface Shape permits CircleShape, RectangleShape, TriangleShape {
double area();
}
record CircleShape(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
}
record RectangleShape(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
}
record TriangleShape(double base, double height) implements Shape {
@Override
public double area() {
return 0.5 * base * height;
}
}
class SealedPatternMatching {
public static void demonstrateSealedHierarchy() {
System.out.println("\n=== Sealed Classes and Exhaustive Pattern Matching ===");
Shape[] shapes = {
new CircleShape(5.0),
new RectangleShape(4.0, 6.0),
new TriangleShape(3.0, 4.0)
};
for (Shape shape : shapes) {
describeShape(shape);
}
}
public static void describeShape(Shape shape) {
// Exhaustive pattern matching with sealed classes
// The compiler knows all possible implementations
String description = switch (shape) {
case CircleShape c -> "Circle with radius " + c.radius() + " and area " + c.area();
case RectangleShape r -> "Rectangle " + r.width() + "x" + r.height() + " with area " + r.area();
case TriangleShape t -> "Triangle base " + t.base() + " height " + t.height() + " with area " + t.area();
// No default needed - all permitted types are covered
};
System.out.println(description);
}
// Pattern matching in instanceof with sealed classes
public static void processShape(Shape shape) {
if (shape instanceof CircleShape c) {
System.out.println("Processing circle: " + c.radius());
} else if (shape instanceof RectangleShape r) {
System.out.println("Processing rectangle: " + r.width() + "x" + r.height());
} else if (shape instanceof TriangleShape t) {
System.out.println("Processing triangle: " + t.base() + "x" + t.height());
}
}
}
5. Switch Expressions with Pattern Matching
Pattern Matching in Switch (Java 21+)
public class SwitchPatternMatching {
public static void main(String[] args) {
demonstrateSwitchPatterns();
demonstrateGuardedPatterns();
demonstrateNullHandling();
}
public static void demonstrateSwitchPatterns() {
System.out.println("=== Pattern Matching in Switch ===");
Object[] objects = {
"Hello World",
42,
3.14,
List.of(1, 2, 3),
new int[]{1, 2, 3},
null,
'A'
};
for (Object obj : objects) {
String result = describeObject(obj);
System.out.println(obj + " -> " + result);
}
}
public static String describeObject(Object obj) {
return switch (obj) {
case String s -> "String: \"" + s + "\" with length " + s.length();
case Integer i -> "Integer: " + i + " (squared: " + (i * i) + ")";
case Double d -> "Double: " + d + " (formatted: " + String.format("%.2f", d) + ")";
case List<?> list -> "List with " + list.size() + " elements: " + list;
case int[] array -> "int array with " + array.length + " elements";
case null -> "Null object";
default -> "Unknown type: " + obj.getClass().getSimpleName();
};
}
public static void demonstrateGuardedPatterns() {
System.out.println("\n=== Guarded Patterns ===");
Object[] numbers = {10, -5, 0, 100, -1, 50};
for (Object number : numbers) {
String category = categorizeNumber(number);
System.out.println(number + " -> " + category);
}
}
public static String categorizeNumber(Object 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 > 0 -> "Positive double: " + d;
case Double d when d < 0 -> "Negative double: " + d;
case Double d -> "Zero double";
case null -> "Null number";
default -> "Not a number: " + number.getClass().getSimpleName();
};
}
public static void demonstrateNullHandling() {
System.out.println("\n=== Null Handling in Switch ===");
Object[] possiblyNull = {"Hello", null, "World", null, "!"};
for (Object obj : possiblyNull) {
String result = handlePossiblyNull(obj);
System.out.println(obj + " -> " + result);
}
}
public static String handlePossiblyNull(Object obj) {
return switch (obj) {
case null -> "Handling null case explicitly";
case String s when s.length() > 5 -> "Long string: " + s;
case String s -> "Short string: " + s;
default -> "Other: " + obj;
};
}
// Complex example with multiple patterns
public static String processComplexObject(Object obj) {
return switch (obj) {
case String s when s.isEmpty() -> "Empty string";
case String s when s.length() == 1 -> "Single character: " + s;
case String s -> "String: " + s;
case Integer i when i == 0 -> "Zero";
case Integer i when i > 0 && i < 100 -> "Small positive integer";
case Integer i when i >= 100 -> "Large positive integer";
case Integer i -> "Negative integer";
case List<?> list when list.isEmpty() -> "Empty list";
case List<?> list when list.size() == 1 -> "Single element list";
case List<?> list -> "List with " + list.size() + " elements";
case Map<?, ?> map when map.isEmpty() -> "Empty map";
case Map<?, ?> map -> "Map with " + map.size() + " entries";
case null -> "Null object";
default -> "Unhandled type: " + obj.getClass().getSimpleName();
};
}
}
6. Performance Considerations
Benchmarking Pattern Matching
import java.util.*;
public class PatternMatchingPerformance {
public static void main(String[] args) {
demonstratePerformanceComparison();
}
public static void demonstratePerformanceComparison() {
System.out.println("=== Pattern Matching Performance ===");
List<Object> testData = generateTestData(10000);
// Warm up
for (int i = 0; i < 1000; i++) {
traditionalApproach(testData.get(i % testData.size()));
patternMatchingApproach(testData.get(i % testData.size()));
}
// Benchmark traditional approach
long traditionalStart = System.nanoTime();
for (Object obj : testData) {
traditionalApproach(obj);
}
long traditionalTime = System.nanoTime() - traditionalStart;
// Benchmark pattern matching approach
long patternStart = System.nanoTime();
for (Object obj : testData) {
patternMatchingApproach(obj);
}
long patternTime = System.nanoTime() - patternStart;
System.out.printf("Traditional approach: %,d ns%n", traditionalTime);
System.out.printf("Pattern matching: %,d ns%n", patternTime);
System.out.printf("Difference: %.2f%%%n",
((double) patternTime / traditionalTime - 1) * 100);
}
public static String traditionalApproach(Object obj) {
if (obj instanceof String) {
String s = (String) obj;
return s.toUpperCase();
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
return String.valueOf(i * 2);
} else if (obj instanceof List) {
List<?> list = (List<?>) obj;
return "List size: " + list.size();
} else {
return "Unknown";
}
}
public static String patternMatchingApproach(Object obj) {
if (obj instanceof String s) {
return s.toUpperCase();
} else if (obj instanceof Integer i) {
return String.valueOf(i * 2);
} else if (obj instanceof List<?> list) {
return "List size: " + list.size();
} else {
return "Unknown";
}
}
private static List<Object> generateTestData(int size) {
List<Object> data = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < size; i++) {
int type = random.nextInt(4);
switch (type) {
case 0 -> data.add("String_" + i);
case 1 -> data.add(i);
case 2 -> data.add(List.of(i, i+1, i+2));
case 3 -> data.add((double) i);
}
}
return data;
}
// Performance tips
public static void showPerformanceTips() {
System.out.println("\n=== Performance Tips ===");
System.out.println("""
1. Pattern matching has similar performance to traditional approach
2. The JVM can optimize both approaches effectively
3. Focus on readability rather than micro-optimizations
4. Complex patterns with guards may have slight overhead
5. Use the most specific pattern first for better performance
""");
}
}
7. Best Practices and Common Pitfalls
Pattern Matching Best Practices
import java.util.*;
public class PatternMatchingBestPractices {
public static void main(String[] args) {
demonstrateBestPractices();
demonstrateCommonPitfalls();
demonstrateRefactoringExamples();
}
public static void demonstrateBestPractices() {
System.out.println("=== Best Practices ===");
Object obj = "Best Practices Example";
// ✅ GOOD: Simple and readable
if (obj instanceof String s) {
System.out.println("String: " + s);
}
// ✅ GOOD: Combine with other conditions
if (obj instanceof String s && s.length() > 10) {
System.out.println("Long string: " + s);
}
// ✅ GOOD: Use in switch expressions for exhaustive handling
String result = switch (obj) {
case String s -> "String: " + s;
case Integer i -> "Integer: " + i;
case null -> "Null";
default -> "Other";
};
System.out.println("Switch result: " + result);
}
public static void demonstrateCommonPitfalls() {
System.out.println("\n=== Common Pitfalls ===");
Object obj = "Test";
// ❌ BAD: Unnecessary pattern variable
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // s is used
}
// ✅ GOOD: Use traditional instanceof if you don't need the variable
if (obj instanceof String) {
System.out.println("It's a string");
}
// ❌ BAD: Pattern variable scope misunderstanding
// if (obj instanceof String s || s.length() > 0) { } // Compilation error
// ✅ GOOD: Understand pattern variable scope
if (obj instanceof String s && s.length() > 0) {
System.out.println("Valid string: " + s);
}
// ❌ BAD: Overly complex patterns
Object complex = Map.of("data", List.of(1, 2, 3));
if (complex instanceof Map<?, ?> map &&
map.get("data") instanceof List<?> list &&
list.size() > 0 &&
list.get(0) instanceof Integer first) {
System.out.println("First element: " + first);
}
// ✅ GOOD: Consider extracting complex logic
processComplexObject(complex);
}
private static void processComplexObject(Object obj) {
if (obj instanceof Map<?, ?> map) {
Object data = map.get("data");
if (data instanceof List<?> list && !list.isEmpty()) {
Object first = list.get(0);
if (first instanceof Integer i) {
System.out.println("First element: " + i);
}
}
}
}
public static void demonstrateRefactoringExamples() {
System.out.println("\n=== Refactoring Examples ===");
// Before refactoring - traditional approach
Object traditionalObj = "Hello";
String traditionalResult;
if (traditionalObj instanceof String) {
String s = (String) traditionalObj;
traditionalResult = s.toUpperCase();
} else if (traditionalObj instanceof Integer) {
Integer i = (Integer) traditionalObj;
traditionalResult = String.valueOf(i * 2);
} else {
traditionalResult = "Unknown";
}
System.out.println("Traditional: " + traditionalResult);
// After refactoring - pattern matching
Object patternObj = "Hello";
String patternResult = switch (patternObj) {
case String s -> s.toUpperCase();
case Integer i -> String.valueOf(i * 2);
default -> "Unknown";
};
System.out.println("Pattern matching: " + patternResult);
// Complex refactoring example
processUserInputRefactored("[email protected]");
processUserInputRefactored(25);
processUserInputRefactored(null);
}
// Before refactoring
public static void processUserInputTraditional(Object input) {
if (input instanceof String) {
String s = (String) input;
if (s.contains("@")) {
System.out.println("Email: " + s);
} else {
System.out.println("Username: " + s);
}
} else if (input instanceof Integer) {
Integer i = (Integer) input;
if (i >= 0) {
System.out.println("Age: " + i);
} else {
System.out.println("Invalid age");
}
} else if (input == null) {
System.out.println("Null input");
}
}
// After refactoring with pattern matching
public static void processUserInputRefactored(Object input) {
switch (input) {
case String s when s.contains("@") -> System.out.println("Email: " + s);
case String s -> System.out.println("Username: " + s);
case Integer i when i >= 0 -> System.out.println("Age: " + i);
case Integer i -> System.out.println("Invalid age: " + i);
case null -> System.out.println("Null input");
default -> System.out.println("Unknown input type");
}
}
public static void showCodeQualityTips() {
System.out.println("\n=== Code Quality Tips ===");
System.out.println("""
✅ DO:
- Use pattern matching to eliminate boilerplate casting
- Combine with switch expressions for exhaustive handling
- Use guards for additional conditions
- Consider sealed classes for exhaustive pattern matching
- Use meaningful variable names in patterns
❌ DON'T:
- Use pattern variables you don't need
- Create overly complex nested patterns
- Forget about pattern variable scope rules
- Use pattern matching where simple instanceof suffices
- Ignore null handling in patterns
""");
}
}
Conclusion
Key Benefits of Pattern Matching for instanceof:
- Reduced Boilerplate: Eliminates explicit casting
- Improved Readability: More concise and expressive code
- Enhanced Safety: Compiler helps prevent type-related bugs
- Better Maintainability: Clearer intent and structure
When to Use Pattern Matching:
| Scenario | Recommendation |
|---|---|
| Type checking with casting | ✅ Perfect use case |
| Complex conditional logic | ✅ Great for readability |
| Simple type checking only | ❌ Use traditional instanceof |
| Performance-critical code | ⚠️ Benchmark both approaches |
Java Version Support:
- Java 14-15: Preview feature (enable with
--enable-preview) - Java 16+: Standard feature
- Java 21+: Enhanced with switch pattern matching
Pattern matching for instanceof is a significant improvement to Java's type system, making code more concise, readable, and less error-prone. It's particularly powerful when combined with records, sealed classes, and switch expressions.