Constructor và từ khóa this trong Java: khởi tạo object
Nắm vững cú pháp constructor, default constructor, kỹ thuật overload, chaining bằng this() và cách dùng this để phân biệt field với parameter.
TL;DR: Constructor là phương thức đặc biệt cùng tên với class, không có kiểu trả về, chạy đúng một lần khi object được sinh ra qua từ khóa new. Java tự cấp một default constructor không tham số nếu class chưa định nghĩa constructor nào. Từ khóa this đại diện cho đối tượng hiện tại, dùng để phân biệt field với tham số cùng tên (this.x = x) hoặc gọi constructor khác cùng class (this(...)) ở dòng đầu tiên. Nhớ lại bài Class và object, new cấp phát heap trước khi constructor chạy.
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 đủ từng field mỗi lần là thiết kế kém: dễ quên và dễ để object ở trạng thái dở dang. Constructor là cơ chế của Java đảm bảo object luôn được khởi tạo toàn vẹn ngay khi gọi new.
Bài này giải thích cú pháp constructor, default constructor được JVM cấp tự động, kỹ thuật nạp chồng constructor kèm chaining qua this(...), và lý do pattern this.field = field xuất hiện khắp nơi.
1. Đơn đặt hàng máy tính phản ánh vai trò của constructor ra sao?
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 ráp xong mới bàn giao chiếc máy hoàn chỉnh. Form đặt hàng là tham số constructor, còn chiếc máy hoàn chỉnh là 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 là method đặc biệt chạy một lần duy nhất 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 sử dụng.
Thử ngẫmđể người dùng class tự gán field sau khi new giống cửa hàng giao máy tính "rỗng": bạn từng thấy bug nào vì object bị dùng khi chưa gán đủ field chưa?
2. Cú pháp khai báo constructor trong Java có gì đặc biệt?
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 hoạt động và biến mất khi nào?
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 được framework sử dụng (như Jackson JSON deserialize yêu cầu no-arg constructor). Sau đó bạn thêm constructor public Book(String title) thì Java không sinh default nữa, khiến framework báo lỗi "no default constructor". Hãy luôn thêm lại public Book() {} nếu cần.
4. Từ khóa this đại diện cho điều gì trong đối tượng?
Trong constructor và instance method, từ khóa this trỏ tới chính object đang thực thi.
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 đó.

Tham số title che khuất (shadow) trường dữ liệu title. Trong thân hàm, viết title ám chỉ parameter. Dùng this.title để ép trình biên dịch hiểu đó là field của object hiện tại.
Pattern this.x = x cực kỳ phổ biến trong Java: đặt parameter cùng tên field cho rõ nghĩa, dùng this. để phân biệt.
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 khong bat buoc
}
}
Nhiều dự án quy định luôn dùng this. cho mọi truy cập field để tăng tính nhất quán và tránh nhầm lẫn với biến cục bộ.
Thử ngẫmcoding convention ở chỗ bạn làm có bắt buộc this. cho mọi field access không, kể cả khi không bị shadow? Lý do thật sự là gì ngoài tính nhất quán?
5. Kỹ thuật overload constructor được áp dụng như thế nào?
Bạn có thể cung cấp nhiều constructor với các 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 đề: các constructor trên xuất hiện code trùng lặp. Constructor 1 và 2 thực chất chỉ là gọi constructor 3 với các giá trị mặc định. Java cho phép gọi constructor khác bằng this(...).
6. Constructor chaining với this(...) hoạt động theo quy tắc nào?
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;
}
}
Quy tắc bắt buộc:
this(...)phải là câu lệnh đầu tiên trong thân constructor.- Chỉ gọi tối đa 1 lần
this(...)trong mỗi constructor (tránh tạo chu kỳ gọi vòng tròn).
Lý do this(...) phải đứng đầu: Java cần bảo đảm chuỗi khởi tạo chạy đúng thứ tự từ lớp cha xuống lớp con khi có kế thừa.
7. Vì sao constructor cấm trả về giá trị và không nên return sớm?
public class Book {
public Book(String title) {
if (title == null) return; // OK — return khong kem gia tri
// return false; // COMPILE ERROR — constructor khong tra gia tri
this.title = title;
}
}
Lệnh return; không kèm giá trị được phép dùng để thoát constructor sớm. Tuy nhiên, pattern này dễ dẫn đến object ở trạng thái dở dang khi các trường dữ liệu quan trọng chưa được gán đủ.
Thay vì return sớm, bạn nên ném ngoại lệ (exception):
public Book(String title) {
if (title == null) throw new IllegalArgumentException("title must not be null");
this.title = title;
}
Khi constructor ném exception, new sẽ không trả về reference, ngăn caller nhận về một đối tượng lỗi.
8. JVM biên dịch constructor thành phương thức init bên dưới ra sao?
Bytecode không có từ khóa constructor riêng. Compiler chuyển đổi mỗi constructor thành method đặc biệt mang tên <init>. Thao tác new thực chất là cấp phát heap rồi gọi invokespecial <init>.
new Book
dup
ldc "Java"
invokespecial Book.<init>(String)V
new: cấp phát bộ nhớ trên heap và đẩy reference vào stack.dup: nhân đôi reference trên stack (một để trả về, một truyền vào<init>).invokespecial: gọi<init>với tham số, kiểu trả về là void (V).
Do dùng invokespecial (dispatch tĩnh), constructor không thể bị override như các method thông thường.
9. Những cạm bẫy constructor nào thường khiến lập trình viên mất thời gian?
❌ Nhầm 1: Thêm kiểu trả về void vào trước tên class.
public void Book(String title) { this.title = title; } // day la method binh thuong, khong phai constructor
✅ Constructor không có return type. Hãy xóa void.
❌ Nhầm 2: Thêm constructor có tham số rồi quên giữ lại constructor mặc định.
public Book(String title) { ... }
// Jackson hoac Hibernate goi new Book() se gay loi
✅ Khai báo thêm public Book() { } nếu các thư viện hoặc framework yêu cầu.
❌ Nhầm 3: Tự gán tham số cho chính nó do quên từ khóa this.
public Book(String title) { title = title; } // parameter tu gan cho parameter, field van null
✅ Viết rõ ràng: this.title = title;.
❌ Nhầm 4: Đặt this(...) sau các câu lệnh khác.
public Book(String t) {
System.out.println("init");
this(t, 0); // COMPILE ERROR — call to this must be first statement
}
✅ Luôn đưa this(...) lên dòng đầu tiên của constructor.
❌ Nhầm 5: Thoát sớm bằng return; khi dữ liệu không hợp lệ.
public Book(String title) {
if (title == null) return; // object van ton tai voi title = null
this.title = title;
}
✅ Ném IllegalArgumentException để chặn đứng quá trình tạo object lỗi.
10. Oracle JLS quy định chi tiết về constructor và default constructor ra sao?
Spec / reference chính thức:
- JLS §8.8 — Constructor Declarations: cú pháp khai báo và quy tắc đặt tên.
- JLS §8.8.7 — Constructor Body: quy tắc explicit constructor invocation
this(...)phải đứng đầu. - JLS §8.8.9 — Default Constructor: điều kiện sinh default constructor và modifier tương ứng.
- JVMS §2.9.1 — Special Methods: cơ chế dispatch qua chỉ lệnh bytecode
invokespecial.
11. Những điểm cốt lõi nào cần ghi nhớ về constructor và this?
- Constructor là phương thức đặc biệt cùng tên với class, không có return type, dùng để khởi tạo trạng thái hợp lệ cho object.
- Java chỉ tự động cung cấp default constructor không tham số khi class hoàn toàn chưa định nghĩa bất kỳ constructor nào.
- Dùng
this.field = paramđể giải quyết shadowing, và dùngthis(...)ở dòng đầu tiên để chaining giữa các constructor nạp chồng. - Luôn ném ngoại lệ khi dữ liệu khởi tạo không hợp lệ thay vì return sớm, ngăn chặn việc tạo ra các đối tượng ở trạng thái dở dang.
12. Tự kiểm tra mức độ nắm vững constructor và từ khóa this
- 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 có giúp bạn hiểu bản chất không?
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.
Class và object trong Java: bản thiết kế và thực thể heap
Bài tiếpField, local và static trong Java: phân biệt ba vòng đời biến
Lộ trình Junior Java Backend Developer
Chặng 3/21 · OOP: class, kế thừa, đa hình, abstraction — bài 2/16 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này