Pattern Matching
Sau bài này, bạn sẽ:
- Hiểu Pattern Matching for instanceof (Java 16+) — type check + cast + assign trong 1 bước
- Nắm được Pattern Matching for switch (Java 21) — type patterns trong switch expressions
- Biết Record Patterns (Java 21) — destructure records trực tiếp trong patterns
- Kết hợp với sealed classes cho exhaustive switch (compiler check)
- Áp dụng pattern matching để loại bỏ explicit casting và if-else chains
Bài trước: Sealed Classes — Đã học về kiểm soát inheritance hierarchy. Bài này sẽ tìm hiểu Pattern Matching — cách viết type checks ngắn gọn và an toàn hơn.
Giới thiệu
Pattern matching là một tính năng mạnh mẽ giúp viết code ngắn gọn và type-safe hơn khi kiểm tra và extract data từ objects. Java giới thiệu pattern matching từng bước:
- Java 16: Pattern matching for
instanceof(standard) - Java 21: Pattern matching for
switch(standard) - Java 21: Record patterns (standard)
Hành trình Pattern Matching trong Java
Từ preview đến standard mất 2-3 phiên bản — Java Team rất cẩn trọng thiết kế từng loại pattern.
Pattern Matching for instanceof (Java 16)
Quy trình thực thi Pattern Matching
Trước Java 16: Ép kiểu tường minh
// Traditional approach - verbose
public void processObject(Object obj) {
if (obj instanceof String) {
String str = (String) obj; // Ép kiểu tường minh (explicit cast)
System.out.println("String length: " + str.length());
} else if (obj instanceof Integer) {
Integer num = (Integer) obj; // Ép kiểu tường minh (explicit cast)
System.out.println("Number value: " + num);
}
}
Vấn đề:
- Lặp lại type check và cast
- Có thể quên cast
- Verbose, khó đọc
Java 16+: Biến mẫu
// Pattern matching - concise
public void processObject(Object obj) {
if (obj instanceof String str) { // Type check + cast + assign
System.out.println("String length: " + str.length());
} else if (obj instanceof Integer num) {
System.out.println("Number value: " + num);
}
}
obj instanceof String str làm 3 việc cùng lúc:
- Check type: obj có phải String không?
- Cast: Cast obj sang String
- Assign: Gán vào biến mẫu
str
Scope của biến mẫu
public void example(Object obj) {
if (obj instanceof String str) {
System.out.println(str.length()); // OK - trong scope
}
// System.out.println(str); // ERROR - ngoài scope
// Scope với logical operators
if (obj instanceof String str && str.length() > 5) {
System.out.println(str.toUpperCase()); // OK
}
// Scope với negation
if (!(obj instanceof String str)) {
// str KHÔNG available ở đây
} else {
System.out.println(str); // OK - trong else block
}
}
Flow-sensitive scope
Compiler thông minh hiểu flow control:
public void smartScope(Object obj) {
if (!(obj instanceof String str)) {
return; // Early return
}
// Compiler biết: nếu đến đây thì obj chắc chắn là String
System.out.println(str.toUpperCase()); // OK
}
public void withLogicalOperators(Object obj) {
// AND - str available bên phải
if (obj instanceof String str && !str.isEmpty()) {
System.out.println(str); // OK
}
// OR - str KHÔNG available bên phải
if (!(obj instanceof String str) || str.isEmpty()) {
// str có thể null
}
}
Pattern Matching for switch (Java 21)
Switch Expressions (Java 14)
Trước khi có pattern matching, Java 14 đã có switch expressions:
// Traditional switch statement
String result;
switch (dayOfWeek) {
case MONDAY:
case FRIDAY:
result = "6";
break;
case TUESDAY:
result = "7";
break;
default:
result = "0";
}
// Switch expression (Java 14)
String result = switch (dayOfWeek) {
case MONDAY, FRIDAY -> "6";
case TUESDAY -> "7";
default -> "0";
};
Type Patterns trong Switch (Java 21)
// Before - instanceof chain
public String formatValue(Object obj) {
if (obj instanceof Integer i) {
return String.format("int %d", i);
} else if (obj instanceof Long l) {
return String.format("long %d", l);
} else if (obj instanceof Double d) {
return String.format("double %.2f", d);
} else if (obj instanceof String s) {
return String.format("String '%s'", s);
} else {
return obj.toString();
}
}
// After - switch với type patterns
public String formatValue(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %.2f", d);
case String s -> String.format("String '%s'", s);
case null -> "null value";
default -> obj.toString();
};
}
Java 21 switch cho phép explicit case null, trước đây switch sẽ throw NullPointerException với null value.
Mẫu có điều kiện (Guarded Patterns)
// Mẫu có điều kiện (guarded patterns) với 'when'
public String categorizeString(String str) {
return switch (str) {
case null -> "null";
case String s when s.isEmpty() -> "empty";
case String s when s.length() < 5 -> "short";
case String s when s.length() < 10 -> "medium";
default -> "long";
};
}
// Multiple conditions
public double calculateDiscount(Object customer) {
return switch (customer) {
case PremiumCustomer p when p.getYears() > 5 -> 0.20;
case PremiumCustomer p -> 0.15;
case RegularCustomer r when r.getYears() > 2 -> 0.10;
case RegularCustomer r -> 0.05;
default -> 0.0;
};
}
Cơ chế đánh giá Switch Pattern Matching với Guards
Quy tắc ưu tiên trong guarded patterns
Thứ tự các case rất quan trọng — compiler kiểm tra từ trên xuống và chọn case đầu tiên khớp:
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
public String classify(Shape shape) {
return switch (shape) {
// ⚠️ Case cụ thể (có when) phải đặt TRƯỚC case tổng quát
case Circle c when c.radius() > 100 -> "Hình tròn lớn";
case Circle c when c.radius() > 10 -> "Hình tròn trung bình";
case Circle c -> "Hình tròn nhỏ";
case Rectangle r when r.w() == r.h() -> "Hình vuông";
case Rectangle r -> "Hình chữ nhật";
};
}
// ❌ COMPILE ERROR — case tổng quát trước case cụ thể:
// case Circle c -> "tròn"; // Bắt hết Circle
// case Circle c when c.radius() > 10 -> "tròn lớn"; // Không bao giờ đến!
Case A thống trị case B nếu mọi giá trị khớp B đều khớp A. Compiler sẽ báo lỗi nếu case bị thống trị (unreachable):
return switch (obj) {
case CharSequence cs -> "char sequence";
case String s -> "string"; // ❌ ERROR: dominated by CharSequence
default -> "other";
};
String là subtype của CharSequence, nên case String không bao giờ đạt được → compile error. Phải đảo thứ tự: subtype trước, supertype sau.
Quy tắc thống trị chi tiết
Compiler kiểm tra dominance theo 3 loại:
1. Type dominance: Supertype thống trị subtype
// ❌ CharSequence bao gồm cả String
case CharSequence cs -> "charseq";
case String s -> "string"; // Unreachable!
// ✅ Subtype trước
case String s -> "string";
case CharSequence cs -> "charseq";
2. Guarded vs Unguarded: Case không có when thống trị case có when (cùng type)
// ❌ Unguarded bắt hết
case String s -> "any string";
case String s when s.isEmpty() -> "empty"; // Unreachable!
// ✅ Guarded trước
case String s when s.isEmpty() -> "empty";
case String s -> "any string";
3. Constant vs Type pattern: Type pattern thống trị constant pattern
sealed interface Coin permits Penny, Nickel {}
final class Penny implements Coin {}
final class Nickel implements Coin {}
// ❌ Type pattern bắt hết
case Coin c -> "any coin";
case Penny p -> "penny"; // Unreachable!
// ✅ Constant/specific trước
case Penny p -> "penny";
case Coin c -> "other coin";
Tính đầy đủ (Exhaustiveness) với Sealed Classes
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
// Exhaustive - KHÔNG cần default
public double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
// Không cần default - compiler biết đã cover hết
};
}
// Nếu thêm Square vào Shape, code này sẽ COMPILE ERROR
// Compiler bắt phải thêm case Square
Null handling trong Pattern Switch
Java 21 switch có những quy tắc null quan trọng:
// ❌ Trước Java 21: switch luôn throw NPE với null
Object obj = null;
switch (obj) { // NullPointerException!
case String s -> "string";
default -> "other";
}
// ✅ Java 21: Explicit null case
Object obj = null;
String result = switch (obj) {
case null -> "null value"; // Bắt null
case String s -> "string";
default -> "other";
};
// ✅ Combined null + default
String result = switch (obj) {
case String s -> "string";
case null, default -> "null or other"; // Hợp lệ!
};
Quy tắc:
- Nếu KHÔNG có
case null→ switch throw NPE khi nhận null (giống trước Java 21) case nullchỉ đứng ở top-level, không lồng trong guarded patterncase null, defaultlà cú pháp hợp lệ — xử lý cả null và các giá trị còn lạicase nullKHÔNG bị dominate bởicase Object o— null không phải instance của bất kỳ type nào
Record Patterns (Java 21)
Phân rã (Deconstruction) với Record Patterns
Records có thể được "destructured" trực tiếp trong pattern:
record Point(int x, int y) {}
// Before - accessor methods
public void printPoint(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println("Point at (" + x + ", " + y + ")");
}
}
// After - record pattern deconstruction
public void printPoint(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println("Point at (" + x + ", " + y + ")");
}
}
Record Patterns trong Switch
record Point(int x, int y) {}
public String describePoint(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == 0 && y == 0 ->
"Origin";
case Point(int x, int y) when x == 0 ->
"On Y-axis at " + y;
case Point(int x, int y) when y == 0 ->
"On X-axis at " + x;
case Point(int x, int y) when x == y ->
"On diagonal at (" + x + ", " + y + ")";
case Point(int x, int y) ->
"Point at (" + x + ", " + y + ")";
default ->
"Not a point";
};
}
Nested Record Patterns
Records chứa records khác có thể destructured theo cấp:
record Point(int x, int y) {}
record Rectangle(Point topLeft, Point bottomRight) {}
record Circle(Point center, double radius) {}
public String describeShape(Object obj) {
return switch (obj) {
// Nested destructuring
case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) ->
String.format("Rectangle from (%d,%d) to (%d,%d)",
x1, y1, x2, y2);
case Circle(Point(int x, int y), double r) ->
String.format("Circle at (%d,%d) with radius %.2f",
x, y, r);
default -> "Unknown shape";
};
}
// Usage
var rect = new Rectangle(
new Point(0, 0),
new Point(10, 20)
);
System.out.println(describeShape(rect));
// Rectangle from (0,0) to (10,20)
Generic Record Patterns
record Box<T>(T value) {}
public String describeBox(Object obj) {
return switch (obj) {
case Box(String s) -> "Box containing string: " + s;
case Box(Integer i) -> "Box containing integer: " + i;
case Box(var v) -> "Box containing: " + v;
default -> "Not a box";
};
}
var stringBox = new Box<>("hello");
var intBox = new Box<>(42);
System.out.println(describeBox(stringBox)); // Box containing string: hello
System.out.println(describeBox(intBox)); // Box containing integer: 42
Phân rã lồng nhau nhiều cấp
Record patterns có thể lồng nhau sâu tùy ý:
record Street(String name, int number) {}
record Address(Street street, String city) {}
record Person(String name, Address address) {}
// Phân rã 3 cấp
public String formatAddress(Object obj) {
return switch (obj) {
case Person(var name, Address(Street(var streetName, var num), var city)) ->
name + " sống tại số " + num + " " + streetName + ", " + city;
default -> "Không phải Person";
};
}
var person = new Person("An", new Address(new Street("Nguyễn Huệ", 123), "TP.HCM"));
System.out.println(formatAddress(person));
// An sống tại số 123 Nguyễn Huệ, TP.HCM
Unnamed patterns _ (Java 21 preview, Java 22+)
Khi không cần dùng biến trong pattern, dùng _ (underscore):
// Java 22+: Unnamed pattern variable
return switch (shape) {
case Circle(var radius) -> Math.PI * radius * radius;
case Rectangle(var w, _) when w == 0 -> 0; // Không cần height
case Rectangle(var w, var h) -> w * h;
};
Phân loại các loại Pattern trong Java
Java 21 cung cấp nhiều loại patterns khác nhau, mỗi loại phục vụ mục đích riêng:
Ví dụ: JSON Parser
sealed interface JsonValue permits
JsonObject, JsonArray, JsonString, JsonNumber, JsonBoolean, JsonNull {}
record JsonObject(Map<String, JsonValue> fields) 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 {}
// Pretty print JSON với pattern matching
public String prettyPrint(JsonValue json, int indent) {
String spaces = " ".repeat(indent);
return switch (json) {
case JsonNull() ->
"null";
case JsonBoolean(boolean value) ->
String.valueOf(value);
case JsonNumber(double value) ->
String.valueOf(value);
case JsonString(String value) ->
"\"" + value + "\"";
case JsonArray(List<JsonValue> elements) -> {
if (elements.isEmpty()) yield "[]";
StringBuilder sb = new StringBuilder("[\n");
for (int i = 0; i < elements.size(); i++) {
sb.append(spaces).append(" ");
sb.append(prettyPrint(elements.get(i), indent + 2));
if (i < elements.size() - 1) sb.append(",");
sb.append("\n");
}
sb.append(spaces).append("]");
yield sb.toString();
}
case JsonObject(Map<String, JsonValue> fields) -> {
if (fields.isEmpty()) yield "{}";
StringBuilder sb = new StringBuilder("{\n");
var entries = fields.entrySet().iterator();
while (entries.hasNext()) {
var entry = entries.next();
sb.append(spaces).append(" \"")
.append(entry.getKey()).append("\": ");
sb.append(prettyPrint(entry.getValue(), indent + 2));
if (entries.hasNext()) sb.append(",");
sb.append("\n");
}
sb.append(spaces).append("}");
yield sb.toString();
}
};
}
Ví dụ: Command Handler
sealed interface Command permits CreateUser, UpdateUser, DeleteUser {}
record CreateUser(String username, String email) implements Command {}
record UpdateUser(long id, String email) implements Command {}
record DeleteUser(long id) implements Command {}
// Command handler với pattern matching
public String handleCommand(Command command) {
return switch (command) {
case CreateUser(String username, String email) -> {
// Validation với guarded pattern có thể làm riêng
if (username.isBlank() || email.isBlank()) {
yield "Error: username and email required";
}
// Create user logic
yield "Created user: " + username;
}
case UpdateUser(long id, String email) when id > 0 -> {
// Update user logic
yield "Updated user " + id + " with email: " + email;
}
case UpdateUser(long id, String email) ->
"Error: Invalid user ID";
case DeleteUser(long id) when id > 0 -> {
// Delete user logic
yield "Deleted user: " + id;
}
case DeleteUser(long id) ->
"Error: Invalid user ID";
};
}
Ví dụ: Expression Evaluator
sealed interface Expr permits Const, Add, Mul, Neg {}
record Const(int value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
record Neg(Expr expr) implements Expr {}
// Evaluate với nested record patterns
public int eval(Expr expr) {
return switch (expr) {
case Const(int value) -> value;
case Add(Expr left, Expr right) -> eval(left) + eval(right);
case Mul(Expr left, Expr right) -> eval(left) * eval(right);
case Neg(Expr e) -> -eval(e);
};
}
// Simplify expressions
public Expr simplify(Expr expr) {
return switch (expr) {
// 0 + x = x
case Add(Const(int a), Expr x) when a == 0 -> simplify(x);
// x + 0 = x
case Add(Expr x, Const(int b)) when b == 0 -> simplify(x);
// a + b = (a+b) when both const
case Add(Const(int a), Const(int b)) -> new Const(a + b);
// 0 * x = 0
case Mul(Const(int a), Expr x) when a == 0 -> new Const(0);
// x * 0 = 0
case Mul(Expr x, Const(int b)) when b == 0 -> new Const(0);
// 1 * x = x
case Mul(Const(int a), Expr x) when a == 1 -> simplify(x);
// x * 1 = x
case Mul(Expr x, Const(int b)) when b == 1 -> simplify(x);
// a * b = (a*b) when both const
case Mul(Const(int a), Const(int b)) -> new Const(a * b);
// -(-x) = x
case Neg(Neg(Expr x)) -> simplify(x);
// -(a) = -a when const
case Neg(Const(int a)) -> new Const(-a);
// Recursive simplify
case Add(Expr left, Expr right) ->
new Add(simplify(left), simplify(right));
case Mul(Expr left, Expr right) ->
new Mul(simplify(left), simplify(right));
case Neg(Expr e) ->
new Neg(simplify(e));
default -> expr;
};
}
// Usage
Expr expr = new Add(
new Mul(new Const(0), new Const(5)), // 0 * 5
new Const(10)
);
System.out.println(eval(expr)); // 10
System.out.println(simplify(expr)); // Const(10)
Pattern Matching Best Practices
1. Prefer patterns over instanceof chains
// BAD - instanceof chain
public String process(Object obj) {
if (obj instanceof String) {
String s = (String) obj;
return s.toUpperCase();
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
return "Number: " + i;
} else {
return "Unknown";
}
}
// GOOD - pattern matching switch
public String process(Object obj) {
return switch (obj) {
case String s -> s.toUpperCase();
case Integer i -> "Number: " + i;
default -> "Unknown";
};
}
2. Use guarded patterns for complex conditions
// GOOD - Clear intent với when clause
public String categorize(Person person) {
return switch (person) {
case Person p when p.age() < 18 -> "Minor";
case Person p when p.age() < 65 -> "Adult";
case Person p -> "Senior";
};
}
3. Tận dụng tính đầy đủ (exhaustiveness)
// GOOD - Sealed types + no default = exhaustive
sealed interface Status permits Active, Inactive, Suspended {}
record Active() implements Status {}
record Inactive() implements Status {}
record Suspended() implements Status {}
public String getStatusMessage(Status status) {
return switch (status) { // No default needed!
case Active() -> "User is active";
case Inactive() -> "User is inactive";
case Suspended() -> "User is suspended";
};
}
// Compiler ensures all cases handled
4. Use record patterns for cleaner code
// BAD - Verbose
public double distance(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
return Math.sqrt(x * x + y * y);
}
return 0;
}
// GOOD - Concise
public double distance(Object obj) {
return switch (obj) {
case Point(int x, int y) -> Math.sqrt(x * x + y * y);
default -> 0;
};
}
Bytecode và Performance
Pattern matching switch được compile thành gì?
// Source
return switch (obj) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
Compiler tạo bytecode tương đương:
// Equivalent bytecode logic (simplified)
if (obj instanceof String) {
return ((String) obj).length();
} else if (obj instanceof Integer) {
return ((Integer) obj);
} else {
return 0;
}
JIT optimizations:
- Type profiles: JIT biết actual types tại runtime → inline trực tiếp
- Sealed type optimization: Với sealed types, JIT có thể dùng
tableswitchthay vìlookupswitch - Null check elimination: Nếu có
case nullriêng, JIT tách null check ra trước
Kết luận: Pattern matching switch KHÔNG chậm hơn if-else chains. JIT compiler optimize tương đương hoặc tốt hơn nhờ type information.
Performance
Pattern matching không có performance penalty:
- Compile-time feature, bytecode tương tự code truyền thống
- JIT compiler optimize giống nhau
- Switch với patterns có thể được optimize thành table switch
Comparison với Functional Languages
| Language | Pattern Matching |
|---|---|
| Scala | Full pattern matching từ đầu |
| Kotlin | when expressions với pattern matching |
| Haskell | Advanced pattern matching với guards |
| Java 21 | Type patterns, record patterns, guarded patterns |
Java pattern matching gần với các functional languages nhưng vẫn giữ Java syntax.
Future: Pattern Matching
Pattern matching vẫn đang phát triển trong Java. Có thể có thêm:
- Array patterns: Destructure arrays
- Collection patterns: Match collections
- String patterns: Pattern matching cho strings
- Named patterns: Reusable patterns
- Scope của biến mẫu: Biến chỉ valid trong nhánh (branch) của nó — không dùng được ngoài
ifhoặccase - Quy tắc thống trị (dominance): Subtype phải đặt TRƯỚC supertype trong switch — nếu không sẽ compile error
nullcase: Phải xử lý tường minh trong switch —case nullhoặc NullPointerException- Pattern variable và
&&:obj instanceof String s && s.length() > 5hợp lệ, nhưng||thì KHÔNG:obj instanceof String s || s.length() > 5❌ - Guarded patterns:
whenclause chỉ trong switch (Java 21), không tronginstanceof - Record patterns chỉ hoạt động với
record, không với class thường
// ✅ && — str available vì nếu instanceof false, && short-circuits
if (obj instanceof String str && str.length() > 5) { ... }
// ❌ || — Compile Error!
if (obj instanceof String str || str.length() > 5) { ... }
// Nếu obj KHÔNG phải String → str chưa được gán → undefined!
Quy tắc: Pattern variable chỉ "chắc chắn gán" (definitely assigned) trong nhánh mà pattern match thành công. Với ||, nhánh phải có thể thành công ngay cả khi pattern KHÔNG match → biến chưa gán → compile error.
Object obj = null;
// ❌ Không có case null → NPE!
switch (obj) {
case String s -> "string";
default -> "other"; // default KHÔNG bắt null!
}
// ✅ case null phải explicit
switch (obj) {
case null -> "null";
case String s -> "string";
default -> "other";
}
// ✅ Hoặc combined
switch (obj) {
case String s -> "string";
case null, default -> "null or other";
}
Quan trọng: default KHÔNG bắt null! Phải có case null explicit hoặc case null, default.
- Type pattern:
T vmatches nếu targetinstanceof Tlà true, bind vàov - Record pattern:
R(p1, p2)matches nếu targetinstanceof R, rồi recursively match components - Guarded pattern:
pattern when exprmatches nếu pattern matches VÀ expr là true - Dominance: Pattern A dominates B nếu mọi value match B cũng match A
- Exhaustiveness: Switch expression trên sealed type phải cover tất cả permitted subclasses (trực tiếp hoặc qua hierarchy)
- Null KHÔNG match bất kỳ type pattern nào — phải dùng
case nullexplicit
Tham khảo: JLS §14.30
Thử thách: Output hay Compile Error?
Câu 1
Object obj = "Hello";
if (obj instanceof String s) {
System.out.println(s.length());
}
System.out.println(s.toUpperCase()); // Dòng này?
Đáp án
Compile error. Biến s chỉ có scope trong block if. Dòng System.out.println(s.toUpperCase()) nằm ngoài scope → compiler không tìm thấy biến s.
Câu 2
record Point(int x, int y) {}
Object obj = new Point(3, 4);
String result = switch (obj) {
case Point(int x, int y) when x > y -> "x wins";
case Point(int x, int y) -> "default point";
case null -> "null";
default -> "other";
};
System.out.println(result);
Đáp án
Output: default point
Point(3, 4) khớp case đầu tiên Point(int x, int y) when x > y → 3 > 4 là false → không khớp. Chuyển sang case tiếp Point(int x, int y) → khớp vì không có điều kiện → trả về "default point".
Câu 3
sealed interface Animal permits Dog, Cat {}
record Dog(String name) implements Animal {}
record Cat(String name) implements Animal {}
String result = switch ((Animal) new Dog("Rex")) {
case Dog d -> "Dog: " + d.name();
// Thiếu case Cat
};
Đáp án
Compile error. Switch trên sealed type Animal phải exhaustive. Thiếu case Cat → compiler báo lỗi "the switch expression does not cover all possible input values". Phải thêm case Cat c -> ... hoặc default.
Kết luận
Evolution của pattern matching trong Java
| Version | Feature |
|---|---|
| Java 16 | Pattern matching for instanceof |
| Java 17 | Sealed classes (foundation for exhaustiveness) |
| Java 21 | Pattern matching for switch, Record patterns |
Lợi ích
✅ Code ngắn gọn hơn: Ít boilerplate ✅ Type-safe: Compile-time checking ✅ Null-safe: Explicit null handling ✅ Maintainable: Exhaustiveness checking ✅ Readable: Intent rõ ràng
Bài tập thực hành
Bài 1: instanceof patterns
Refactor đoạn code sau sử dụng pattern matching:
public double calculateArea(Object shape) {
if (shape instanceof Circle) {
Circle c = (Circle) shape;
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.width() * r.height();
}
return 0;
}
Bài 2: Switch với type patterns
Viết method String formatValue(Object obj) sử dụng switch expression để format:
Integer: "Integer: %d"Double: "Double: %.2f"String: "String: '%s'"List: "List with %d elements"null: "null"- Others: toString()
Bài 3: Record patterns
Tạo sealed interface Shape với records Circle(Point center, double radius) và Rectangle(Point topLeft, Point bottomRight).
Viết method boolean contains(Shape shape, Point point) sử dụng record patterns để kiểm tra point có nằm trong shape không.
Tài liệu tham khảo
- JEP 394: Pattern Matching for instanceof
- JEP 441: Pattern Matching for switch
- JEP 440: Record Patterns
- Oracle Pattern Matching Guide