1. Cú pháp nạp chồng constructor
Trong thực tế hiếm khi tất cả đối tượng được tạo theo cùng một cách. Hãy hình dung lớp Person (Con người). Đôi khi ta cần tạo một người khi chỉ biết tên của họ. Đôi khi — là tên và tuổi. Và cũng có khi — ta chẳng biết gì cả, cứ để tất cả ở trạng thái mặc định. Sẽ thật kỳ lạ nếu bắt người dùng của lớp lúc nào cũng phải truyền mọi tham số, ngay cả khi họ không cần.
Nạp chồng constructor là cách để cho người dùng lớp có quyền chọn: tham số nào họ muốn truyền khi tạo đối tượng và tham số nào để mặc định. Điều này khiến lớp trở nên linh hoạt và dễ sử dụng.
So sánh:
Trong xưởng thiết kế, người ta lắp ráp ô tô. Có người đặt bản cơ bản (không điều hòa, chỉ có vô lăng và bánh xe), có người muốn “lux” (ghế sưởi, Wi‑Fi và xe tự lái). Nhưng chiếc xe vẫn là cùng một lớp, chỉ là các cách lắp khác nhau!
Nạp chồng là khi trong một lớp có nhiều constructor nhưng khác nhau về tham số (khác số lượng, kiểu hoặc thứ tự tham số). Tất cả chúng đều có tên trùng với tên lớp và không có kiểu trả về.
Ví dụ: Lớp với các constructor được nạp chồng
public class Person {
String name;
int age;
// Constructor không có tham số (mặc định)
public Person() {
this.name = "Không rõ";
this.age = 0;
}
// Constructor với một tham số
public Person(String name) {
this.name = name;
this.age = 0;
}
// Constructor với hai tham số
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Giờ ta có thể tạo đối tượng theo nhiều cách:
Person p1 = new Person(); // tên = "Không rõ", tuổi = 0
Person p2 = new Person("Vasya"); // tên = "Vasya", tuổi = 0
Person p3 = new Person("Petya", 25); // tên = "Petya", tuổi = 25
Java xác định dùng constructor nào như thế nào?
Java dựa vào số lượng và kiểu đối số bạn truyền sau new. Nếu bạn viết new Person("Vasya"), trình biên dịch sẽ tìm constructor với một tham số kiểu String. Nếu bạn viết new Person("Petya", 25), cần constructor có tham số String và int.
2. Gọi một constructor từ constructor khác: this(...)
Đôi khi khi nạp chồng constructor, một phần mã bị lặp lại. Ví dụ, bạn muốn mọi constructor đều bắt buộc đặt tên, còn tuổi nếu không truyền thì mặc định là 0. Để không sao chép cùng một logic trong mỗi constructor, bạn có thể gọi một constructor khác bằng từ khóa this(...).
Ví dụ
public class Person {
String name;
int age;
// Constructor với hai tham số
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Constructor với một tham số gọi constructor khác
public Person(String name) {
this(name, 0); // gọi Person(String name, int age)
}
// Constructor không có tham số gọi constructor khác
public Person() {
this("Không rõ", 0);
}
}
public Person(String name) {
this(name, 0); // gọi Person(String, int)
}
Quy tắc quan trọng: Lệnh gọi constructor khác qua this(...) phải là dòng đầu tiên của constructor!
Tại sao cần điều này?
- Giảm lặp mã.
- Nếu bạn muốn thay đổi logic khởi tạo (ví dụ, thay giá trị mặc định), bạn chỉ cần sửa ở một nơi.
- Mã sạch hơn và dễ bảo trì.
3. Ví dụ thực tế: nạp chồng trong một lớp thực
Giả sử ta có lớp Account — tài khoản ngân hàng. Đôi khi ta chỉ biết tên chủ tài khoản, đôi khi muốn chỉ định luôn số dư ban đầu, và có lúc — cả đơn vị tiền tệ nữa.
Ví dụ về lớp có các constructor được nạp chồng
public class Account {
String owner;
double balance;
String currency;
// Constructor với ba tham số
public Account(String owner, double balance, String currency) {
this.owner = owner;
this.balance = balance;
this.currency = currency;
}
// Constructor với hai tham số (tiền tệ mặc định — "EUR")
public Account(String owner, double balance) {
this(owner, balance, "EUR");
}
// Constructor với một tham số (balance = 0, currency = "EUR")
public Account(String owner) {
this(owner, 0.0, "EUR");
}
// Constructor không có tham số (owner — "Không rõ", balance = 0, currency = "EUR")
public Account() {
this("Không rõ", 0.0, "EUR");
}
public void printInfo() {
System.out.println(owner + ": " + balance + " " + currency);
}
}
Sử dụng
public class Main {
public static void main(String[] args) {
Account acc1 = new Account("Ivan", 1000, "USD");
Account acc2 = new Account("Mariya", 500);
Account acc3 = new Account("Pyotr");
Account acc4 = new Account();
acc1.printInfo(); // Ivan: 1000.0 USD
acc2.printInfo(); // Mariya: 500.0 EUR
acc3.printInfo(); // Pyotr: 0.0 EUR
acc4.printInfo(); // Không rõ: 0.0 EUR
}
}
Tại sao điều này tiện lợi?
- Có thể tạo đối tượng với nhiều mức độ chi tiết khác nhau.
- Không cần lúc nào cũng chỉ định mọi tham số (đặc biệt khi chúng thường giống nhau).
- Dễ mở rộng lớp: nếu xuất hiện tham số mới, bạn có thể thêm một constructor khác.
4. Các lỗi điển hình khi nạp chồng constructor
Lỗi số 1: Nhầm lẫn kiểu và thứ tự tham số.
Nếu bạn có hai constructor — Person(String name, int age) và Person(int age, String name) — trình biên dịch sẽ phân biệt được, nhưng với người dùng của lớp thì điều đó có thể cực kỳ rối rắm. Tốt nhất nên tránh các tình huống như vậy.
Lỗi số 2: Thiếu constructor mặc định.
Nếu bạn chỉ khai báo các constructor có tham số, rồi sau đó cố gắng tạo đối tượng không có tham số — bạn sẽ nhận lỗi biên dịch. Hãy luôn thêm constructor không tham số nếu bạn cần dùng đến nó.
Lỗi số 3: Cố gắng gọi constructor khác không ở dòng đầu tiên.
Lệnh gọi this(...) luôn phải là dòng đầu tiên của constructor. Nếu viết bất cứ thứ gì trước đó — sẽ có lỗi biên dịch.
Lỗi số 4: Gọi constructor lặp thành vòng vô hạn.
Nếu một constructor gọi chính nó (trực tiếp hoặc qua chuỗi gọi), điều này sẽ dẫn đến lỗi biên dịch do đệ quy vô hạn.
Lỗi số 5: Trường chưa được khởi tạo.
Nếu bạn quên khởi tạo một trường nào đó trong constructor (hoặc trong chuỗi gọi), đối tượng có thể rơi vào trạng thái không hợp lệ. Hãy đảm bảo tất cả trường có giá trị hợp lý sau khi tạo đối tượng.
GO TO FULL VERSION