Chuyển tới nội dung chính

Pattern Matching

Mục tiêu bài học

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 đề:

  1. Lặp lại type check và cast
  2. Có thể quên cast
  3. 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);
}
}
Biến mẫu (pattern variable)

obj instanceof String str làm 3 việc cùng lúc:

  1. Check type: obj có phải String không?
  2. Cast: Cast obj sang String
  3. 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();
};
}
null handling

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!
Quy tắc thống trị (Dominance Rule)

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:

  1. Nếu KHÔNG có case null → switch throw NPE khi nhận null (giống trước Java 21)
  2. case null chỉ đứng ở top-level, không lồng trong guarded pattern
  3. case null, default là cú pháp hợp lệ — xử lý cả null và các giá trị còn lại
  4. case null KHÔNG bị dominate bởi case 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 tableswitch thay vì lookupswitch
  • Null check elimination: Nếu có case null riê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

Zero runtime overhead

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

LanguagePattern Matching
ScalaFull pattern matching từ đầu
Kotlinwhen expressions với pattern matching
HaskellAdvanced pattern matching với guards
Java 21Type 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
OCP Exam Tips
  • 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 if hoặc case
  • Quy tắc thống trị (dominance): Subtype phải đặt TRƯỚC supertype trong switch — nếu không sẽ compile error
  • null case: Phải xử lý tường minh trong switch — case null hoặc NullPointerException
  • Pattern variable và &&: obj instanceof String s && s.length() > 5 hợp lệ, nhưng || thì KHÔNG: obj instanceof String s || s.length() > 5
  • Guarded patterns: when clause chỉ trong switch (Java 21), không trong instanceof
  • Record patterns chỉ hoạt động với record, không với class thường
OCP Trap — Pattern variable scope: && works, || does NOT
// ✅ && — 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.

OCP Trap — null trong pattern switch
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.

📖 Theo JLS §14.30 — Pattern Matching
  • Type pattern: T v matches nếu target instanceof T là true, bind vào v
  • Record pattern: R(p1, p2) matches nếu target instanceof R, rồi recursively match components
  • Guarded pattern: pattern when expr matches 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 null explicit

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

VersionFeature
Java 16Pattern matching for instanceof
Java 17Sealed classes (foundation for exhaustiveness)
Java 21Pattern 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)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

Đọc thêm