CodeGym /Các khóa học /JAVA 25 SELF /Pattern Matching trong switch (Java 17/21+)

Pattern Matching trong switch (Java 17/21+)

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Switch cổ điển: những hạn chế của quá khứ

Hãy nhớ lại, switch trong Java trước khi xuất hiện pattern matching như thế nào. Thẳng thắn mà nói, khá bảo thủ. Nó chỉ làm việc với các kiểu nguyên thủy (int, char, và sau đó là enum, String), và nếu bạn muốn xử lý các kiểu đối tượng khác nhau — bạn phải viết chuỗi if-else dài.

Object obj = ...;
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println("Chuỗi: " + s);
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    System.out.println("Số: " + i);
} else if (obj == null) {
    System.out.println("null!");
} else {
    System.out.println("Thứ gì đó khác");
}

Tiện không? Không hẳn. Mã cồng kềnh, dễ mắc lỗi, và nếu thêm một kiểu mới — lại phải viết lại chuỗi if-else.

2. Pattern Matching trong switch: cuộc cách mạng ở Java 17/21+

Java 17 (preview) và cuối cùng là Java 21+ đã giới thiệu phiên bản mới của switch, hỗ trợ pattern matching. Giờ đây switch có thể làm việc không chỉ với kiểu nguyên thủy mà còn với mọi đối tượng, kiểu dữ liệu, thậm chí với null, và hỗ trợ điều kiện bổ sung (guard).

Cú pháp mới: ngắn gọn nhưng mạnh mẽ

Ví dụ đơn giản về pattern matching trong switch:

Object obj = ...;
switch (obj) {
    case String s -> System.out.println("Chuỗi: " + s);
    case Integer i -> System.out.println("Số: " + i);
    case null -> System.out.println("null!");
    default -> System.out.println("Thứ gì đó khác");
}

Điều gì đang diễn ra ở đây?

  • Nếu obj là chuỗi, biến s được tự động ép về kiểu String.
  • Nếu obj là số nguyên, biến i trở thành Integer.
  • Nếu objnull, nhánh chuyên biệt sẽ chạy.
  • Mọi thứ còn lại — default.

Tại sao điều này tiện lợi?

  • Không cần ép kiểu thủ công: Java tự bảo đảm an toàn kiểu.
  • Ít mã hơn: Không còn ngoặc thừa, cast và lặp lại.
  • An toàn hơn: Trình biên dịch kiểm tra bạn không bỏ sót các trường hợp có thể xảy ra (đặc biệt với các lớp sealed, sẽ bàn sau).
  • Dễ bổ sung kiểu mới: Chỉ cần thêm một nhánh case nữa.

3. Pattern Matching trong switch: ví dụ cơ bản

Ví dụ 1: Xử lý nhiều kiểu

Object obj = 42;

switch (obj) {
    case String s -> System.out.println("Chuỗi: " + s);
    case Integer i -> System.out.println("Số: " + i);
    case null -> System.out.println("Đây là null");
    default -> System.out.println("Kiểu không xác định");
}

Kết quả:

Số: 42

Ví dụ 2: Sử dụng guard (when)

Đôi khi chỉ biết kiểu là chưa đủ — bạn muốn thêm điều kiện bổ sung.

Object obj = "Hello, world!";

switch (obj) {
    case String s when s.length() > 10 -> System.out.println("Chuỗi dài: " + s);
    case String s -> System.out.println("Chuỗi ngắn: " + s);
    case Integer i when i > 0 -> System.out.println("Số dương: " + i);
    case Integer i -> System.out.println("Số không dương: " + i);
    default -> System.out.println("Thứ gì đó khác");
}

Kết quả:

Chuỗi dài: Hello, world!

Lưu ý:

  • when là một guard, kiểm tra bổ sung cho case.
  • Nếu điều kiện không thỏa, sẽ kiểm tra các case tiếp theo.

Ví dụ 3: Xử lý null

Object obj = null;

switch (obj) {
    case null -> System.out.println("Đây là null!");
    default -> System.out.println("Không phải null");
}

Kết quả:

Đây là null!

4. Pattern Matching trong switch với lớp và kế thừa

Pattern matching trong switch hoạt động đặc biệt tốt với các hệ phân cấp lớp.

sealed interface Shape permits Circle, Rectangle, Square {}

final class Circle implements Shape {
    final double radius;
    Circle(double radius) { this.radius = radius; }
}

final class Rectangle implements Shape {
    final double width, height;
    Rectangle(double width, double height) { this.width = width; this.height = height; }
}

final class Square implements Shape {
    final double side;
    Square(double side) { this.side = side; }
}
Shape shape = new Rectangle(2, 3);

String description = switch (shape) {
    case Circle c -> "Đường tròn có bán kính " + c.radius;
    case Rectangle r -> "Hình chữ nhật " + r.width + "x" + r.height;
    case Square s -> "Hình vuông có cạnh " + s.side;
};
System.out.println(description);

Kết quả:

Hình chữ nhật 2.0x3.0

Mẹo: Nếu bạn dùng các lớp sealed và đã xử lý tất cả biến thể của chúng, không cần nhánh default — trình biên dịch hiểu rằng không còn trường hợp nào khác!

5. Sử dụng switch với các lớp record và pattern lồng nhau

Pattern matching trong switch hỗ trợ làm việc với các lớp record và các pattern lồng nhau (từ Java 21+).

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

String result = switch (obj) {
    case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
        "Đường thẳng từ (" + x1 + "," + y1 + ") đến (" + x2 + "," + y2 + ")";
    case Point(int x, int y) -> "Điểm (" + x + "," + y + ")";
    default -> "Đối tượng không xác định";
};
System.out.println(result);

Kết quả:

Đường thẳng từ (1,2) đến (3,4)

6. Đặc điểm và hạn chế của pattern matching trong switch

Biến chỉ khả dụng bên trong case

Biến được khai báo trong pattern (ví dụ, String s) chỉ tồn tại trong khối case tương ứng. Bên ngoài, nó không khả dụng.

switch (obj) {
    case String s -> System.out.println(s); // s có thể truy cập ở đây
    // System.out.println(s); // lỗi biên dịch ở đây!
}

Xử lý null

  • switch cũ trên đối tượng, cố gắng truyền null dẫn đến NullPointerException.
  • Trong pattern matching switch mới, có thể xử lý rõ ràng null qua case null.

Guard (when)

Điều kiện bổ sung (when) chỉ có trong switch kiểu biểu thức (cú pháp mũi tên ->).

case String s when s.isEmpty() -> System.out.println("Chuỗi rỗng");

Yêu cầu JDK và IDE

  • Pattern matching trong switch là tính năng của Java 17 (preview) và Java 21+ (ổn định).
  • Để sử dụng cần JDK 21 và một IDE hỗ trợ các khả năng mới (ví dụ, IntelliJ IDEA 2023+).

7. Ứng dụng pattern matching switch trong một ứng dụng thực

Xem một ứng dụng học tập — một task-tracker đơn giản. Giả sử bạn có kiểu tác vụ trừu tượng và các biến thể của nó:

sealed interface Task permits SimpleTask, DeadlineTask, MeetingTask {}

final class SimpleTask implements Task {
    final String description;
    SimpleTask(String description) { this.description = description; }
}

final class DeadlineTask implements Task {
    final String description;
    final java.time.LocalDate deadline;
    DeadlineTask(String description, java.time.LocalDate deadline) {
        this.description = description;
        this.deadline = deadline;
    }
}

final class MeetingTask implements Task {
    final String topic;
    final java.time.LocalTime time;
    MeetingTask(String topic, java.time.LocalTime time) {
        this.topic = topic;
        this.time = time;
    }
}

Toàn bộ logic trong một switch gọn gàng:

Task task = new DeadlineTask("Nộp dự án", java.time.LocalDate.of(2024, 7, 1));

String info = switch (task) {
    case SimpleTask st -> "Tác vụ thường: " + st.description;
    case DeadlineTask dt -> "Tác vụ có deadline: " + dt.description + ", đến " + dt.deadline;
    case MeetingTask mt -> "Cuộc họp: " + mt.topic + " lúc " + mt.time;
};
System.out.println(info);

Kết quả:

Tác vụ có deadline: Nộp dự án, đến 2024-07-01

8. Các lỗi thường gặp khi dùng pattern matching trong switch

Lỗi số 1: quên xử lý null. Nếu bạn không thêm case null, còn obj có thể là null — bạn sẽ nhận MatchException (hoặc NullPointerException trong switch cũ). Luôn xử lý tường minh null khi có thể.

Lỗi số 2: không tính hết các lớp con của lớp sealed. Nếu bạn dùng lớp sealed và không xử lý hết các biến thể trong switch, trình biên dịch sẽ báo lỗi hoặc yêu cầu nhánh default. Đừng quên thêm các case mới khi mở rộng hệ phân cấp.

Lỗi số 3: biến pattern không khả dụng bên ngoài khối case. Biến được khai báo trong pattern chỉ tồn tại trong khối mũi tên. Cố gắng dùng nó ngoài khối sẽ gây lỗi biên dịch.

Lỗi số 4: dùng tính năng mới trên JDK cũ hoặc IDE cũ. Pattern matching trong switch yêu cầu Java 21+ và hỗ trợ tương ứng trong IDE của bạn. Nếu dùng phiên bản cũ — sẽ có lỗi biên dịch hoặc gợi ý không chính xác.

Lỗi số 5: nhầm lẫn với guard. Cố gắng dùng when trong switch dạng khối cũ? Sẽ không được. Guard chỉ có cho cú pháp mũi tên ->.

Lỗi số 6: quên nhánh default khi làm việc với các hệ phân cấp mở. Nếu switch của bạn không chỉ làm việc với các lớp sealed, hãy luôn thêm default — nếu không sẽ có trường hợp không được xử lý.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION