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 constructor — nghi 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.

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ướiNhận diện bằng hai dấu hiệu: trùng tên class và khô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!newgiờ 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 = tenxuấ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
SinhVienthiế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 appliedsau 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àngThứ 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ếnsoLuong. Ngày 18 mổ xẻstatictrọ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 classSachhô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ácthis(canh, canh)), và()(1×1).Trong constructor
SinhVien, némIllegalArgumentExceptionkhitenlànull/rỗng (isBlank— Ngày 09) — thử tạo đối tượng "lậu" và xem nó bị chặn.Thêm
static int soLuongtă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!
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.


