1. Đi sâu vào các lớp record
Trước khi bắt đầu với record patterns, hãy cùng ôn lại record-class là gì.
Record — là một lớp gọn nhẹ, bất biến với các phương thức equals, hashCode, toString được tạo tự động, đồng thời tự động tạo getter cho mọi thành phần. Record-classes xuất hiện trong Java 16 và trở thành “món quà” cho những ai đã mệt mỏi với việc tự tay viết các DTO.
// Cổ điển: Point với hai trường
record Point(int x, int y) {}
Tạo đối tượng và truy cập trường:
Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p); // Point[x=10, y=20]
Tại sao cần Record Patterns
Trước Java 21, nếu bạn muốn kiểm tra rằng một đối tượng là record nào đó và lấy các trường của nó, bạn phải viết khá nhiều mã thủ công: kiểm tra kiểu qua instanceof, ép kiểu và gọi getter.
Object obj = new Point(5, 7);
if (obj instanceof Point) {
Point p = (Point) obj;
int x = p.x();
int y = p.y();
System.out.println("x=" + x + ", y=" + y);
}
Nếu record phức tạp hoặc lồng nhau, mã sẽ nhanh chóng biến thành một “rừng” giải nén. Và việc dùng nhận dạng như vậy trong switch trước đây khá bất tiện.
Record patterns cho phép giải nén giá trị của các lớp record ngay trong pattern matching: cả trong instanceof lẫn switch. Điều này giúp mã ngắn gọn hơn, an toàn hơn và dễ hiểu hơn.
2. Cú pháp record patterns: ví dụ đơn giản nhất
record Point(int x, int y) {}
Object obj = new Point(10, 20);
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
Điều gì đang xảy ra ở đây?
- obj instanceof Point(int x, int y) — vừa kiểm tra kiểu, vừa giải nén các thành phần vào biến x và y.
- Các biến x và y chỉ khả dụng bên trong khối if.
Kết quả:
x=10, y=20
Lưu ý: nếu obj không phải Point hoặc là null, khối if sẽ không được thực thi và các biến sẽ không được khai báo.
3. Record patterns trong switch: ngắn gọn và an toàn
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
Object shape = new Point(3, 4);
switch (shape) {
case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
default -> System.out.println("Unknown shape!");
}
- Trong case Point(int x, int y) chúng ta có ngay x và y.
- Trong case Circle(Point center, int r) có sẵn center và r.
Kết quả:
Point: x=3, y=4
Với các hệ phân cấp sealed, switch trở nên bao quát đầy đủ: nếu quên xử lý một trường hợp, trình biên dịch sẽ cảnh báo.
4. Record patterns lồng nhau: giải nén lồng nhau
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Object obj = new Line(new Point(1, 2), new Point(3, 4));
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}
Kết quả:
Line from (1,2) to (3,4)
Làm việc với cây, đồ thị, cấu trúc phức tạp? Các pattern lồng nhau là trợ thủ đắc lực của bạn.
5. Record patterns với biểu thức guard (when)
Object obj = new Point(0, 100);
if (obj instanceof Point(int x, int y) && x == 0) {
System.out.println("Điểm nằm trên trục Y: y=" + y);
}
Trong switch, làm như sau với when:
switch (obj) {
case Point(int x, int y) when x == 0 -> System.out.println("Trên trục Y: y=" + y);
case Point(int x, int y) when y == 0 -> System.out.println("Trên trục X: x=" + x);
case Point(int x, int y) -> System.out.println("Điểm bình thường");
default -> System.out.println("Không phải điểm");
}
6. Ứng dụng record patterns trong ứng dụng thực tế
Hãy lấy ví dụ thực tế — các hình học.
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
switch (shape) {
case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
case Circle(Point(int x, int y), int radius) ->
System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
case Rectangle(Point(int x, int y), int width, int height) ->
System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
default -> System.out.println("Unknown shape");
}
}
Ví dụ gọi:
printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));
Kết quả:
Rectangle: topLeft=(5, 10), size=20x30
7. Hạn chế và lưu ý khi dùng record patterns
Chỉ dành cho lớp record. Record patterns chỉ hoạt động với các đối tượng thực sự là record-class.
class NotARecord {
int a, b;
NotARecord(int a, int b) { this.a = a; this.b = b; }
}
// Lỗi! Không phải record
// if (obj instanceof NotARecord(int a, int b)) { ... }
Phải khớp số lượng và kiểu của thành phần. Mẫu phải tương ứng với các thành phần của record-class.
record Pair(int a, String b) {}
// Lỗi: kiểu không khớp
// if (obj instanceof Pair(String a, int b)) { ... }
Biến chỉ khả dụng bên trong khối. Tên được khai báo trong pattern chỉ nhìn thấy trong phần thân if hoặc case cụ thể nơi việc so khớp thành công.
8. Mẫu lồng nhau: ví dụ với cây
sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}
record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
return switch (expr) {
case NumberExpr(int value) -> value;
case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
};
}
Ví dụ sử dụng:
Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14
Ở đây chúng ta giải nén trực tiếp NumberExpr(int value), PlusExpr(Expr left, Expr right) và MinusExpr(Expr left, Expr right) — mã trở nên ngắn gọn và diễn đạt.
9. Bảng: so sánh pattern matching với và không có record patterns
| Cách | Ép kiểu thủ công | Pattern Matching với record patterns |
|---|---|---|
| Kiểm tra kiểu | |
|
| Ép kiểu | |
Không cần |
| Truy cập trường | |
|
| Giải nén lồng nhau | Rất nhiều mã thủ công | Các pattern lồng nhau |
| Sử dụng trong switch | Bất tiện/không thể | Dễ dàng, ngắn gọn |
10. Lỗi thường gặp khi dùng record patterns
Lỗi №1: cố áp dụng record pattern cho lớp không phải record. Nếu bạn viết một lớp thông thường rồi cố “giải nén” nó như record, trình biên dịch sẽ không cho phép. Record patterns chỉ hoạt động với record-class thực sự.
Lỗi №2: không khớp số lượng hoặc kiểu thành phần. Mẫu phải khớp hoàn toàn với chữ ký các thành phần của record. Ví dụ, với record Pair(int a, String b) không thể viết Pair(int x, int y).
Lỗi №3: cố dùng biến của pattern bên ngoài khối. Các tên khai báo trong mẫu chỉ khả dụng trong if/case tương ứng. Bên ngoài khối, các biến đó không tồn tại.
Lỗi №4: dùng record patterns trên phiên bản JDK cũ. Hỗ trợ xuất hiện trong Java 21. Trên Java 17 trở xuống sẽ gặp lỗi cú pháp. Hãy kiểm tra phiên bản JDK và hỗ trợ trong IDE.
Lỗi №5: pattern lồng nhau quá sâu. Khả năng lồng nhau rất mạnh, nhưng đừng làm phức tạp nếu không cần thiết: cấu trúc quá sâu làm giảm khả năng đọc và tăng rủi ro lỗi.
GO TO FULL VERSION