1. Mở rộng lớp record: các phương thức bổ sung
Có thể thêm phương thức vào record không?
Chắc chắn rồi! Record giống như một căn hộ đã hoàn thiện: tường và sàn đã làm xong, bạn không thể thay đổi chúng, nhưng không ai cấm bạn sắp xếp nội thất theo ý mình. Bên trong record bạn có thể khai báo các phương thức thông thường, phương thức static và thậm chí lưu hằng số. Điều đó có nghĩa là business logic không nhất thiết phải đưa vào các lớp “utility” riêng — có thể tích hợp gọn gàng ngay trong chính record.
Ví dụ: phương thức tính khoảng cách giữa các điểm
Giả sử chúng ta có record cho một điểm trên mặt phẳng:
public record Point(int x, int y) {
// Phương thức bổ sung
public double distanceTo(Point other) {
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Bây giờ có thể làm như sau:
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0
Như bạn thấy, record-lớp có thể được “bổ sung” các phương thức của riêng nó — và điều đó rất tiện lợi!
Ví dụ: phương thức static
public record Rectangle(int width, int height) {
public int area() {
return width * height;
}
public static Rectangle square(int size) {
return new Rectangle(size, size);
}
}
Giờ bạn có thể tạo “hình vuông” chỉ bằng một lệnh gọi:
Rectangle r = Rectangle.square(5);
System.out.println(r.area()); // 25
2. Constructor gọn và kiểm tra dữ liệu
Tại sao cần constructor “gọn”?
Constructor chuẩn của record được tạo tự động và gán tham số cho các trường. Nhưng đôi khi ta muốn thêm kiểm tra dữ liệu đầu vào (ví dụ, cấm tọa độ âm).
Trong lớp thông thường, ta sẽ viết:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
if (x < 0 || y < 0) throw new IllegalArgumentException();
this.x = x;
this.y = y;
}
// ...
}
Trong record-lớp có thể khai báo constructor gọn — không lặp lại danh sách tham số và không cần gán trường tường minh (trình biên dịch làm giúp chúng ta).
Cú pháp của constructor gọn
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates must be non-negative");
}
// Không cần viết: this.x = x; this.y = y;
}
}
- Tham số của constructor tự động trùng với các component của record.
- Việc gán this.x = x và this.y = y được trình biên dịch thực hiện tự động sau khi phần thân constructor chạy xong (hoặc sau khi thoát thành công).
- Nếu ném ngoại lệ, đối tượng sẽ không được tạo.
Ví dụ có kiểm tra
Point p1 = new Point(3, 5); // OK
Point p2 = new Point(-1, 2); // Ném IllegalArgumentException!
Có thể khai báo constructor “thông thường” không?
Có, được! Nếu cần tạo constructor với danh sách tham số khác hoặc logic bổ sung, hãy khai báo tường minh:
public record Range(int from, int to) {
public Range(int size) {
this(0, size); // gọi constructor chính
}
}
3. Các hạn chế của lớp record
Khác với lớp thông thường, record-lớp có một số hạn chế. Cần ghi nhớ để không ngạc nhiên trước lỗi biên dịch.
Chỉ component — không có trường không static bổ sung
Trong record-lớp không được khai báo trường không static mới:
public record Person(String name, int age) {
// int id; // Lỗi biên dịch! Không được thêm trường không static.
}
Có thể khai báo các trường và phương thức static:
public record Person(String name, int age) {
public static final String SPECIES = "Homo sapiens";
}
Record luôn final
Record-lớp không thể là lớp cha (không thể kế thừa từ nó) và bản thân nó không thể kế thừa tường minh lớp khác (ngoại trừ kế thừa ngầm từ java.lang.Record). Điều này có nghĩa là record-lớp luôn là cấu trúc “cuối cùng”.
public record User(String login) { }
// public class Admin extends User {} // Lỗi: không thể kế thừa từ record!
Có thể hiện thực interface
Record-lớp có thể hiện thực các interface:
public interface Printable {
void print();
}
public record Invoice(int amount) implements Printable {
@Override
public void print() {
System.out.println("Số tiền: " + amount);
}
}
4. Ví dụ: record mở rộng trong các bài toán thực tế
Hãy xem một vài ví dụ thực tiễn, nơi các phương thức bổ sung và constructor gọn khiến record-lớp trở nên thực sự hữu ích.
Record với phương thức tính toán
public record Circle(double x, double y, double radius) {
public double area() {
return Math.PI * radius * radius;
}
public double distanceTo(Circle other) {
double dx = x - other.x;
double dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Record có kiểm tra hợp lệ
public record Email(String value) {
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Email không hợp lệ: " + value);
}
}
}
Giờ không thể tạo email không hợp lệ:
Email e1 = new Email("test@example.com"); // OK
Email e2 = new Email("not-an-email"); // Ném IllegalArgumentException
Record với các phương thức static bổ sung
public record Temperature(double celsius) {
public static Temperature fromFahrenheit(double fahrenheit) {
return new Temperature((fahrenheit - 32) * 5 / 9);
}
public double toFahrenheit() {
return celsius * 9 / 5 + 32;
}
}
Cách dùng:
Temperature t = Temperature.fromFahrenheit(98.6);
System.out.println(t.celsius()); // 37.0
System.out.println(t.toFahrenheit()); // 98.6
5. Constructor gọn: các lưu ý và hạn chế
Khi nào nên dùng constructor gọn?
- Khi cần kiểm tra tính hợp lệ của dữ liệu (validation).
- Khi cần điều chỉnh giá trị trước khi lưu (ví dụ, làm tròn số hoặc chuyển chuỗi thành chữ hoa).
- Khi muốn tránh lặp lại danh sách tham số.
Đặc điểm hoạt động
- Trong constructor gọn không được gán tường minh giá trị cho các component (this.x = ...) — điều này sẽ gây lỗi biên dịch, vì trình biên dịch tự gán sau khi phần thân constructor chạy xong.
- Trong constructor gọn không thể đổi tên tham số — chúng luôn trùng với tên component của record.
Ví dụ: làm tròn tự động
public record Money(double amount) {
public Money {
amount = Math.round(amount * 100) / 100.0; // Làm tròn tới 2 chữ số thập phân
}
}
6. Thực hành: phát triển ứng dụng học tập
Giả sử chúng ta đang xây dựng các nghiệp vụ ngân hàng trong một ứng dụng học tập. Giả sử có record-lớp Transaction, lưu số tiền, người gửi và người nhận.
public record Transaction(String from, String to, double amount) {
public Transaction {
if (amount <= 0) throw new IllegalArgumentException("Số tiền phải là số dương");
if (from == null || to == null) throw new IllegalArgumentException("Các trường không được là null");
}
public String description() {
return String.format("Chuyển khoản %.2f từ %s đến %s", amount, from, to);
}
}
Cách dùng:
Transaction t = new Transaction("Alice", "Bob", 150.0);
System.out.println(t.description()); // Chuyển khoản 150.00 từ Alice đến Bob
Cố gắng tạo giao dịch không hợp lệ sẽ gây lỗi:
Transaction t2 = new Transaction("Alice", "Bob", -10.0); // IllegalArgumentException
Bảng: điều gì được và không được trong lớp record
| Được trong lớp record | Không được trong lớp record |
|---|---|
| Phương thức thông thường | Trường không static mới |
| Phương thức và trường static | Kế thừa từ các lớp khác |
| Hiện thực interface | Làm lớp cha cho lớp khác |
| Constructor gọn và thông thường | Thay đổi component sau khi tạo |
| Ghi đè phương thức | Sử dụng setter |
7. Các lỗi thường gặp khi làm việc với record-lớp có thân không chuẩn
Lỗi số 1: cố gắng thêm trường không static.
Người mới thường cố thêm vào record-lớp “một trường nữa cho logic nội bộ” — ví dụ, bộ đếm hoặc cache. Điều này sẽ không hoạt động: trình biên dịch sẽ báo lỗi ngay. Nếu bạn cần lưu thêm trạng thái, rất có thể record-lớp không phải lựa chọn phù hợp.
Lỗi số 2: quên validation trong constructor gọn.
Nếu bạn muốn đối tượng luôn hợp lệ, hãy thực hiện kiểm tra trong constructor gọn. Đừng trông chờ vào việc “người dùng sẽ không nhập bậy”.
Lỗi số 3: cố gắng thay đổi component sau khi tạo.
Các trường của record-lớp là final — không thể thay đổi trực tiếp hay qua phương thức. Nếu bạn cần cấu trúc có thể thay đổi — hãy dùng lớp thông thường.
Lỗi số 4: trùng lặp logic trong phương thức và constructor.
Đôi khi logic kiểm tra và tính toán bị trùng lặp cả trong phương thức lẫn constructor. Tốt hơn hãy làm toàn bộ validation trong constructor, còn phương thức chỉ nên chứa business logic “thuần”.
Lỗi số 5: quên các hạn chế về kế thừa.
Record-lớp luôn final — không thể tạo lớp con từ nó. Nếu bạn thiết kế hệ phân cấp cần các lớp con — hãy dùng lớp thông thường.
GO TO FULL VERSION