Constructor và `this` — khởi tạo object đúng cách
Constructor là gì, default constructor, overload constructor, this() chaining, this. để phân biệt field với parameter, và vì sao không return được trong constructor.
Bài trước bạn tạo object bằng new Book() rồi gán từng field một dòng. Nhưng bắt người dùng class phải nhớ gán đủ 5 field mỗi lần là thiết kế kém — dễ quên, dễ để object ở trạng thái dở. Constructor là cơ chế Java đảm bảo object luôn khởi tạo đúng, đầy đủ, ngay khi new.
Bài này giải thích cú pháp constructor, default constructor mà Java cấp miễn phí (và khi nào nó biến mất), overload + chaining bằng this(...), và vì sao this.field = field là pattern phổ biến ai cũng gặp.
1. Analogy — đơn đặt hàng máy tính
Khi mua máy tính lắp ráp, bạn điền form đặt hàng: CPU, RAM, ổ cứng, card đồ họa. Cửa hàng không giao máy "rỗng" rồi bảo bạn tự cắm từng linh kiện — họ lắp xong trả máy hoàn chỉnh. Form = tham số constructor; máy hoàn chỉnh = object vừa tạo.
| Đời thường | Java |
|---|---|
| Form đặt hàng | Parameter constructor |
| Cửa hàng lắp ráp | Body constructor |
| Máy đã hoàn chỉnh trao tay | Object vừa new xong |
Constructor = method đặc biệt chạy một lần khi object được tạo. Mục đích: đảm bảo object ra đời ở trạng thái hợp lệ, sẵn sàng dùng.
2. Cú pháp constructor
public class Book {
String title;
String author;
int year;
// Constructor — khong return type, ten trung ten class
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
}
// Su dung
Book b = new Book("Clean Code", "Robert C. Martin", 2008);
System.out.println(b.title); // Clean Code
Đặc điểm của constructor:
- Không có return type (không
void, khôngint, không gì cả). - Tên trùng tên class — kể cả chữ hoa/chữ thường.
- Gọi bằng
new ClassName(args...). - Có thể có nhiều constructor (overload — bài sau).
2.1 Vì sao không có return type?
Constructor không return giá trị — nó chỉ khởi tạo object mà new sẽ trả về. Nếu bạn viết public void Book(...), Java hiểu đó là method bình thường tên Book, không phải constructor. Compile được nhưng object tạo qua new Book(...) sẽ không chạy method đó, dễ bug khó lần.
public class Book {
// Khong phai constructor — day la method ten "Book"
public void Book(String title) {
this.title = title;
}
}
Book b = new Book(); // goi default constructor, title van null
b.Book("Java"); // goi method Book — gan duoc nhung phai goi tay
3. Default constructor — Java cho không
Nếu class không khai báo constructor nào, Java tự động thêm một constructor public không tham số:
public class Empty { }
// Ngam gan voi:
public class Empty {
public Empty() { }
}
Empty e = new Empty(); // OK
Constructor mặc định không làm gì — chỉ để bạn có thể new object. Field vẫn lấy giá trị default (0, false, null).
Nhưng nếu bạn khai báo bất kỳ constructor nào — dù chỉ có 1 phiên bản — Java không thêm default nữa:
public class Book {
String title;
public Book(String title) { this.title = title; }
}
Book a = new Book("Java"); // OK
Book b = new Book(); // COMPILE ERROR — khong co constructor khong tham so
Muốn cả 2 form, khai báo cả 2:
public Book() { }
public Book(String title) { this.title = title; }
Bạn viết class có default constructor, framework dùng (vd Jackson JSON deserialize yêu cầu no-arg constructor). Rồi bạn thêm constructor public Book(String title) — Java không generate default nữa — framework fail với "no default constructor". Luôn thêm lại public Book() {} nếu cần.
4. this — reference tới object đang khởi tạo / đang chạy method
Trong constructor và instance method, từ khóa this trỏ tới chính object đang chạy.
public class Book {
String title;
public Book(String title) {
this.title = title; // this.title = field; title = parameter
}
}
Đặt this vào đúng vị trí trong trình tự new thì rõ hơn: object đã tồn tại và đã có đủ field trước khi dòng đầu tiên của constructor chạy — this chỉ là cái tên để gọi object đó.

Parameter title shadow field title. Trong body, viết title ám chỉ parameter. Dùng this.title để ép hiểu "field của object này".
Pattern this.x = x cực kỳ phổ biến trong Java — đặt parameter cùng tên field cho rõ ý, dùng this. phân biệt. Không phải anti-pattern.
4.1 Có thể bỏ this. khi không bị shadow
public class Book {
String title;
public Book(String t) {
title = t; // OK — khong co shadow, title hieu la field
this.title = t; // cung OK nhung du thua
}
}
Nhưng nhiều team có rule "luôn dùng this. cho field access" để code nhất quán và tránh nhầm với local. Convention tuỳ codebase.
5. Overload constructor
Có nhiều constructor với signature khác nhau:
public class Book {
String title;
String author;
int year;
public Book(String title) {
this.title = title;
this.author = "Unknown";
this.year = 0;
}
public Book(String title, String author) {
this.title = title;
this.author = author;
this.year = 0;
}
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
}
Người gọi chọn constructor phù hợp với lượng thông tin họ có:
new Book("Java");
new Book("Java", "Bloch");
new Book("Java", "Bloch", 2018);
Vấn đề: 3 constructor có code lặp. Constructor 1 và 2 thực chất chỉ là "gọi constructor 3 với default cho phần thiếu". Java cho phép gọi constructor khác bằng this(...).
6. this(...) — constructor chaining
public class Book {
String title;
String author;
int year;
public Book(String title) {
this(title, "Unknown", 0); // goi constructor 3
}
public Book(String title, String author) {
this(title, author, 0); // goi constructor 3
}
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
}
Rule:
this(...)phải là statement đầu tiên trong constructor.- Chỉ 1
this(...)mỗi constructor (không chain vòng). - Cấm gọi
this(...)sau code khác, cấm gọi nhiều lần.
Lý do rule "phải đầu tiên": Java đảm bảo chain constructor chạy theo thứ tự từ cha đến con (khi có kế thừa — module sau). Cho phép code chạy trước this(...) sẽ phá đảm bảo đó.
7. Cấm return <value> trong constructor
public class Book {
public Book(String title) {
if (title == null) return; // OK — return; (void return) hop le
// return false; // COMPILE ERROR — constructor khong tra gia tri
this.title = title;
}
}
return; (không giá trị) được phép để thoát constructor sớm. return <value>; thì không. Vì constructor không có return type — không có gì để trả.
Nhưng pattern "return sớm để bỏ qua khởi tạo" hay dẫn đến object dở dang — object đã được cấp memory, chạy constructor nửa chừng, rồi về với reference hợp lệ nhưng field chưa gán đủ. Thay vì return sớm, nên ném exception:
public Book(String title) {
if (title == null) throw new IllegalArgumentException("title must not be null");
this.title = title;
}
Constructor ném exception → new không trả reference → caller nhận exception, object không bao giờ "tồn tại".
8. Cơ chế bên dưới — constructor compile thành method <init>
Bytecode không có khái niệm "constructor" riêng. Compiler sinh method tên đặc biệt <init> cho mỗi constructor. new = cấp memory + gọi invokespecial <init>.
new Book
dup
ldc "Java"
invokespecial Book.<init>(String)V
new— cấp memory, để reference trên operand stack.dup— nhân đôi reference (1 để trả về, 1 để pass vào<init>).invokespecial— gọi<init>với argument"Java". Return typeV= void.
Hiểu điều này giải thích tại sao constructor không là instance method đầy đủ (không override được, không polymorphic) — invokespecial dispatch tĩnh, khác invokevirtual.
9. Pitfall tổng hợp
❌ Nhầm 1: Thêm void vào trước tên class.
public void Book(String title) { this.title = title; } // day la method, khong phai constructor
✅ Constructor không có return type. Xóa void.
❌ Nhầm 2: Thêm constructor rồi quên giữ default.
public Book(String title) { ... }
// Framework Jackson goi new Book() -> crash
✅ Thêm public Book() { } lại nếu cần.
❌ Nhầm 3: Gán parameter cho chính nó.
public Book(String title) { title = title; } // tu gan — field van null
✅ this.title = title;.
❌ Nhầm 4: this(...) không phải statement đầu tiên.
public Book(String t) {
System.out.println("init");
this(t, 0); // COMPILE ERROR
}
✅ Chuyển this(...) lên đầu.
❌ Nhầm 5: Throw trong constructor rồi field ở trạng thái dở.
public Book(String title) {
this.title = title;
if (title.length() > 100) throw new IllegalArgumentException(...);
// neu throw, object khong duoc return -> khong van de voi caller
// nhung neu co this register vao static list truoc, se leak
}
✅ Validate trước khi gán/register. Constructor ném exception → caller không lấy được reference → an toàn.
10. 📚 Deep Dive Oracle
Spec / reference chính thức:
- JLS §8.8 — Constructor Declarations — cú pháp đầy đủ, rule tên + modifier.
- JLS §8.8.7 — Constructor Body — rule "explicit constructor invocation phải đầu tiên".
- JLS §8.8.9 — Default Constructor — khi nào Java sinh default, access level thế nào.
- JVMS §2.9.1 — Special Methods — tại sao
<init>được dispatch bằnginvokespecial, không phảiinvokevirtual.
Ghi chú: JLS §8.8.9 nói default constructor kế thừa access modifier từ class — public class Foo → default constructor public; class Foo (package-private) → default constructor package-private. Chi tiết quan trọng khi viết library công khai.
11. Tóm tắt
- Constructor = method đặc biệt khởi tạo object. Cùng tên class, không return type.
- Gọi bằng
new ClassName(args...). JVM cấp memory → gọi constructor → trả reference. - Nếu không khai báo constructor nào, Java cho default no-arg. Khai báo constructor khác → default biến mất.
thistrỏ tới object hiện tại.this.field = fieldlà pattern chuẩn để phân biệt field với parameter cùng tên.- Overload constructor được — signature khác nhau.
this(...)gọi constructor khác cùng class. Phải là statement đầu tiên.return;void được phép;return <value>;không được.- Validate trong constructor bằng throw exception, không return sớm — tránh object dở dang.
- Bytecode: constructor compile thành
<init>, gọi bằnginvokespecial.
12. Tự kiểm tra
- Q1Đoạn sau có chạy đúng ý không?
public class User { String name; public void User(String name) { this.name = name; } } User u = new User("Alice"); System.out.println(u.name); - Q2Đoạn sau in gì?
public class Box { int x; int y; public Box() { this(10, 20); } public Box(int x, int y) { this.x = x; this.y = y; } } Box a = new Box(); Box b = new Box(5, 6); System.out.println(a.x + "," + a.y + " / " + b.x + "," + b.y); - Q3Đoạn sau compile không?
public class Foo { int x; public Foo(int x) { System.out.println("init"); this(0); } public Foo() { this.x = 0; } } - Q4Vì sao đoạn sau không giúp caller "chọn bỏ" object?
public class Order { int total; public Order(int total) { if (total < 0) return; this.total = total; } } Order o = new Order(-5); System.out.println(o.total); - Q5Một class Jackson dùng để deserialize JSON cần constructor thế nào? Code sau có crash không?
public class User { public String name; public User(String name) { this.name = name; } } // Jackson: objectMapper.readValue("{\"name\":\"A\"}", User.class)
Bài tiếp theo: Field vs local — lifecycle và initialization order
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên