CodeGym /Các khóa học /JAVA 25 SELF /Record với các phương thức

Record với các phương thức

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

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 = xthis.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.

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