Backend

99 Ngày Java — Ngày 12: Constructor — nghi thức khai sinh đối tượng

SSite Admin
8 tháng 08, 2026 6 phút đọc 22 lượt xem
99 Ngày Java — Ngày 12: Constructor — nghi thức khai sinh đối tượng

Ngày 11 để lộ một vấn đề: tạo new SinhVien() xong phải nhớ đổ dữ liệu từng field — quên một cái là đối tượng "không có dữ liệu" (namSinh = 0, ten = null) lang thang khắp chương trình, đến Ngày 09 ta còn biết null dẫn đến các lỗi thế nào. Java xử lý vấn đề này này bằng constructornghi thức khai báo: đoạn code bắt buộc chạy ngay lúc new, nơi bạn ép mọi đối tượng được tạo với đầy đủ hành trang.

Sketchnote Ngày 12: constructor trong Java — khai sinh đối tượng, default constructor, overload, this() và khối khởi tạo

Constructor — hàm khởi tạo

public class SinhVien {
    String ten;
    int namSinh;

    // Constructor: TRÙNG TÊN class, KHÔNG có kiểu trả về (kể cả void!)
    public SinhVien(String ten, int namSinh) {
        this.ten = ten;              // this phân xử trùng tên — Ngày 11
        this.namSinh = namSinh;
    }
}

SinhVien an = new SinhVien("An", 2004);   // đúc + đổ dữ liệu trong MỘT bước// SinhVien loi = new SinhVien();          // ✗ hết compile! Vì sao? — ngay bên dưới
  • Nhận diện bằng hai dấu hiệu: trùng tên classkhông khai kiểu trả về — lỡ tay viết void SinhVien() là thành method thường trùng tên, bug khó nhìn kinh điển!

  • new giờ làm ba việc: cấp phát trên heap (Ngày 11) → chạy constructor → trả tham chiếu. Không có cửa nào đúc đối tượng mà né được constructor.

  • Tham số constructor che tên field nên this.ten = ten xuất hiện dày đặc — đúng pattern shadowing Ngày 11 đã báo trước.

  • Lợi ích lớn nhất: bất khả thi ≠ khó xảy ra — không ai có thể tạo SinhVien thiếu tên, thay vì "mong mọi người nhớ gán".

Default constructor — món quà có điều kiện

Khoan — Ngày 11 ta viết new SinhVien() ngon lành dù chưa khai constructor nào? Đó là nhờ quà của compiler, và món quà này có điều kiện:

public class HoaDon { }                 // không khai constructor nào

HoaDon hd = new HoaDon();               // ✓ chạy — nhờ DEFAULT CONSTRUCTOR// Compiler âm thầm tặng: public HoaDon() { }  (rỗng, không tham số)

public class SinhVien {
    public SinhVien(String ten) { }     // vừa khai constructor RIÊNG...
}
// ...là quà default BIẾN MẤT:// new SinhVien()        ✗ lỗi biên dịch// new SinhVien("An")    ✓ chỉ còn cửa này

// → Muốn cả hai? Tự khai cả hai — overloading, như Ngày 10!
  • Luật gọn: không khai gì → được tặng bản rỗng; khai bất kỳ bản nào → quà rút lại. Lỗi constructor SinhVien cannot be applied sau khi thêm constructor là "nghi lễ trưởng thành" ai cũng trải qua.

  • Đây là tính năng, không phải phiền toái: đã ép khai sinh đầy đủ thì đường tắt new SinhVien() phải bị khóa.

  • Nhiều framework (như Jackson bên series Spring) lại cần bản không tham số để tự dựng đối tượng — khi đó khai tường minh thêm một bản rỗng.

Overload constructor & this(...)

Constructor cũng overload được như method Ngày 10 — nhiều "cửa khai sinh" cho nhiều tình huống. Nhưng đừng copy logic qua lại giữa các bản; hãy nối chúng bằng this(...):

public class SinhVien {
    String ten;
    int namSinh;
    double diemTB;

    public SinhVien(String ten, int namSinh, double diemTB) {  // bản ĐẦY ĐỦ
        this.ten = ten;
        this.namSinh = namSinh;
        this.diemTB = diemTB;
    }

    public SinhVien(String ten, int namSinh) {
        this(ten, namSinh, 0.0);       // this(...) GỌI constructor khác — không lặp code
    }

    public SinhVien(String ten) {
        this(ten, 2000);               // dây chuyền: 1 tham số → 2 → 3
    }
}
// Quy tắc: this(...) phải là DÒNG ĐẦU TIÊN của constructor// Mọi đường khởi tạo đều đổ về MỘT bản đầy đủ — sửa logic chỉ sửa một chỗ
  • Mẫu telescoping: bản ít tham số điền giá trị mặc định rồi ủy thác cho bản đầy đủ — mọi kiểm tra dữ liệu chỉ viết một nơi.

  • this(...) phải đứng dòng đầu tiên — Java muốn chắc chắn đối tượng khởi tạo xong trước khi code khác đụng vào.

  • Quá nhiều tham số cùng kiểu dễ truyền nhầm thứ tự? Ghi nhớ cái tên Builder pattern — hẹn Ngày 93.

Khối khởi tạo & thoáng nhìn static

Ngoài constructor, Java còn vài đường khởi tạo phụ — gặp ít hơn nhưng nên nhận mặt để đọc code người khác không bỡ ngỡ:

public class SinhVien {
    static int soLuong;                    // field static — ĐẾM chung cả lớp

    String ma;
    List<String> monHoc = new ArrayList<>();   // 1) gán thẳng tại field

    {                                      // 2) instance initializer — chạy TRƯỚC
        ma = "SV-" + (++soLuong);          //    MỌI constructor, dùng khi các
    }                                      //    constructor cần chung đoạn setup

    static {                               // 3) static initializer — chạy MỘT lần
        System.out.println("Nạp class SinhVien");   //    khi class được nạp
    }
}
// Thứ tự thực thi khi new: field gán thẳng + khối {} (theo thứ tự viết) → constructor// Dùng tiết chế: đa số trường hợp, gán tại field hoặc this(...) là đủ rõ ràng
  • Thứ tự khi new: gán tại field + khối {} chạy theo thứ tự viết, xong hết mới tới constructor.

  • static ở đây là field chung cả lớp — 30 đối tượng, một biến soLuong. Ngày 18 mổ xẻ static trọn vẹn; hôm nay chỉ cần cảm nhận "của lớp" khác "của đối tượng".

  • Lời khuyên thực dụng: 90% nhu cầu chỉ cần constructor + this(...) — khối khởi tạo để dành cho setup thật sự dùng chung.

Bài tập nhỏ

  • Thêm constructor (ten, tacGia, gia) vào class Sach hôm qua — sửa code cũ theo, cảm nhận lỗi "quà bị rút" rồi tự giải quyết.

  • Viết 3 constructor HinhChuNhat: (dai, rong), (canh) (vuông — ủy thác this(canh, canh)), và () (1×1).

  • Trong constructor SinhVien, ném IllegalArgumentException khi tennull/rỗng (isBlank — Ngày 09) — thử tạo đối tượng "lậu" và xem nó bị chặn.

  • Thêm static int soLuong tăng trong constructor — tạo 5 đối tượng, in số lượng, giải thích vì sao dùng chung được.

Kết luận

Đối tượng giờ chào đời tử tế: constructor ép khai sinh đầy đủ ngay tại new, default constructor là quà có điều kiện, overload + this(...) mở nhiều cửa mà logic vẫn một mối, và khối khởi tạo lo phần setup chung. Nhưng field của ta vẫn phơi trần — ai cũng an.diemTB = -99 được, nghi thức khai sinh kỹ mấy cũng thành công cốc. Ngày 13 ta dựng hàng rào đầu tiên của OOP: đóng gói (encapsulation)private, getter/setter, và vì sao giấu dữ liệu lại là bảo vệ nó. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9