🏗️ Hàm Khởi Tạo - Constructor
Chào mừng đến với bài học về Constructor - công thức mở quán cà phê trong Java!
🎯 Món Ăn Hôm Nay
Tưởng tượng bạn mở một quán cà phê mới:
- Khởi tạo quán → Đặt tên, địa chỉ, giờ mở cửa
- Tự động chuẩn bị → Không cần làm thủ công
- Nhiều cách mở → Quán nhỏ, quán lớn, quán VIP
Đó chính là Constructor - hàm khởi tạo object!
🏗️ Constructor Là Gì?
Constructor = Hàm đặc biệt để khởi tạo object (tự động chạy khi tạo object)
public class QuanCafe {
String ten;
String diaChi;
// Constructor
public QuanCafe(String ten, String diaChi) {
this.ten = ten;
this.diaChi = diaChi;
System.out.println("🏪 Khởi tạo quán: " + ten);
}
}
// Sử dụng
QuanCafe quan = new QuanCafe("Java Café", "123 Lê Lợi");
☕ Ẩn Dụ Café:
- Constructor = Công thức mở quán
- Object = Một quán cà phê cụ thể
- this = Chính quán này
- new = Lệnh mở quán mới
📋 Đặc Điểm Constructor
- ✅ Cùng tên với class
- ✅ Không có return type (kể cả void)
- ✅ Tự động gọi khi tạo object với
new - ✅ Có thể overload (nhiều constructor)
- ✅ Khởi tạo giá trị cho thuộc tính
public class MonCaPhe {
String ten;
int gia;
// Constructor
public MonCaPhe(String ten, int gia) { // Không có return type
this.ten = ten;
this.gia = gia;
}
}
👨🍳 Câu Chuyện Trong Quán
Tình huống 1: Constructor Cơ Bản
public class DoUong {
String ten;
int gia;
String size;
// Constructor
public DoUong(String ten, int gia, String size) {
this.ten = ten;
this.gia = gia;
this.size = size;
System.out.println("✅ Đã tạo: " + ten + " " + size + " - " + gia + "đ");
}
public void hienThi() {
System.out.printf("☕ %s (%s): %,d VND%n", ten, size, gia);
}
public static void main(String[] args) {
DoUong latte = new DoUong("Latte", 50000, "M");
DoUong mocha = new DoUong("Mocha", 60000, "L");
System.out.println("\n📋 THÔNG TIN:");
latte.hienThi();
mocha.hienThi();
}
}
Output:
✅ Đã tạo: Latte M - 50000đ
✅ Đã tạo: Mocha L - 60000đ
📋 THÔNG TIN:
☕ Latte (M): 50,000 VND
☕ Mocha (L): 60,000 VND
Tình huống 2: Constructor Overloading
public class QuanCafe {
String ten;
String diaChi;
int soBan;
// Constructor 1: Đầy đủ
public QuanCafe(String ten, String diaChi, int soBan) {
this.ten = ten;
this.diaChi = diaChi;
this.soBan = soBan;
}
// Constructor 2: Không có số bàn (mặc định 10)
public QuanCafe(String ten, String diaChi) {
this(ten, diaChi, 10); // Gọi constructor 1
}
// Constructor 3: Chỉ tên (mặc định địa chỉ và bàn)
public QuanCafe(String ten) {
this(ten, "123 Lê Lợi", 10);
}
public void hienThi() {
System.out.println("🏪 Quán: " + ten);
System.out.println(" Địa chỉ: " + diaChi);
System.out.println(" Số bàn: " + soBan);
System.out.println();
}
public static void main(String[] args) {
QuanCafe quan1 = new QuanCafe("Java Café", "Quận 1", 20);
QuanCafe quan2 = new QuanCafe("Code Café", "Quận 3");
QuanCafe quan3 = new QuanCafe("Dev Café");
quan1.hienThi();
quan2.hienThi();
quan3.hienThi();
}
}
📝 Công Thức Nấu (Code Examples)
Ví Dụ 1: Default Constructor vs Custom Constructor
public class MonAn {
String ten;
int gia;
// Default constructor (nếu không viết, Java tự tạo)
public MonAn() {
ten = "Chưa đặt tên";
gia = 0;
}
// Custom constructor
public MonAn(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}
public void hienThi() {
System.out.printf("%s: %,d VND%n", ten, gia);
}
public static void main(String[] args) {
MonAn m1 = new MonAn(); // Dùng default constructor
MonAn m2 = new MonAn("Latte", 50000); // Dùng custom constructor
m1.hienThi();
m2.hienThi();
}
}
Output:
Chưa đặt tên: 0 VND
Latte: 50,000 VND
Ví Dụ 2: this() - Gọi Constructor Khác
public class DonHang {
String tenKhach;
String mon;
int soLuong;
String size;
// Constructor đầy đủ
public DonHang(String tenKhach, String mon, int soLuong, String size) {
this.tenKhach = tenKhach;
this.mon = mon;
this.soLuong = soLuong;
this.size = size;
}
// Constructor thiếu size → mặc định M
public DonHang(String tenKhach, String mon, int soLuong) {
this(tenKhach, mon, soLuong, "M"); // Gọi constructor đầy đủ
}
// Constructor thiếu số lượng → mặc định 1
public DonHang(String tenKhach, String mon) {
this(tenKhach, mon, 1, "M");
}
public void hienThi() {
System.out.printf("👤 %s: %d × %s (%s)%n",
tenKhach, soLuong, mon, size);
}
public static void main(String[] args) {
DonHang d1 = new DonHang("Minh", "Latte", 2, "L");
DonHang d2 = new DonHang("Lan", "Mocha", 1);
DonHang d3 = new DonHang("Hoa", "Espresso");
d1.hienThi();
d2.hienThi();
d3.hienThi();
}
}
Output:
👤 Minh: 2 × Latte (L)
👤 Lan: 1 × Mocha (M)
👤 Hoa: 1 × Espresso (M)
Ví Dụ 3: Constructor Với Validation
public class SanPham {
String ten;
int gia;
int soLuong;
public SanPham(String ten, int gia, int soLuong) {
// Validation
if (ten == null || ten.isEmpty()) {
this.ten = "Chưa đặt tên";
} else {
this.ten = ten;
}
if (gia < 0) {
this.gia = 0;
System.out.println("⚠️ Giá không hợp lệ! Đặt = 0");
} else {
this.gia = gia;
}
if (soLuong < 0) {
this.soLuong = 0;
System.out.println("⚠️ Số lượng không hợp lệ! Đặt = 0");
} else {
this.soLuong = soLuong;
}
}
public void hienThi() {
System.out.printf("%s: %,d VND (Kho: %d)%n", ten, gia, soLuong);
}
public static void main(String[] args) {
SanPham sp1 = new SanPham("Latte", 50000, 10);
SanPham sp2 = new SanPham("", -5000, -2); // Lỗi
sp1.hienThi();
sp2.hienThi();
}
}
Output:
⚠️ Giá không hợp lệ! Đặt = 0
⚠️ Số lượng không hợp lệ! Đặt = 0
Latte: 50,000 VND (Kho: 10)
Chưa đặt tên: 0 VND (Kho: 0)
Ví Dụ 4: Copy Constructor
public class MonCaPhe {
String ten;
int gia;
boolean conHang;
// Constructor thường
public MonCaPhe(String ten, int gia, boolean conHang) {
this.ten = ten;
this.gia = gia;
this.conHang = conHang;
}
// Copy constructor (sao chép từ object khác)
public MonCaPhe(MonCaPhe mon) {
this.ten = mon.ten;
this.gia = mon.gia;
this.conHang = mon.conHang;
}
public void hienThi() {
System.out.printf("%s: %,d VND - %s%n",
ten, gia, conHang ? "Còn hàng" : "Hết hàng");
}
public static void main(String[] args) {
MonCaPhe latte1 = new MonCaPhe("Latte", 50000, true);
MonCaPhe latte2 = new MonCaPhe(latte1); // Copy từ latte1
System.out.println("Món gốc:");
latte1.hienThi();
System.out.println("\nMón sao chép:");
latte2.hienThi();
// Thay đổi latte2 không ảnh hưởng latte1
latte2.gia = 55000;
latte2.conHang = false;
System.out.println("\nSau khi sửa:");
latte1.hienThi();
latte2.hienThi();
}
}
Ví Dụ 5: Constructor Với Static Counter
public class HoaDon {
static int tongSoHoaDon = 0; // Đếm tổng số hóa đơn
int maHoaDon;
String tenKhach;
int tongTien;
public HoaDon(String tenKhach, int tongTien) {
tongSoHoaDon++; // Tăng bộ đếm
this.maHoaDon = tongSoHoaDon;
this.tenKhach = tenKhach;
this.tongTien = tongTien;
}
public void hienThi() {
System.out.printf("🧾 Hóa đơn #%d: %s - %,d VND%n",
maHoaDon, tenKhach, tongTien);
}
public static void main(String[] args) {
HoaDon hd1 = new HoaDon("Anh Minh", 100000);
HoaDon hd2 = new HoaDon("Chị Lan", 150000);
HoaDon hd3 = new HoaDon("Anh Tùng", 80000);
hd1.hienThi();
hd2.hienThi();
hd3.hienThi();
System.out.println("\n📊 Tổng số hóa đơn: " + HoaDon.tongSoHoaDon);
}
}
Output:
🧾 Hóa đơn #1: Anh Minh - 100,000 VND
🧾 Hóa đơn #2: Chị Lan - 150,000 VND
🧾 Hóa đơn #3: Anh Tùng - 80,000 VND
📊 Tổng số hóa đơn: 3
Ví Dụ 6: Private Constructor (Singleton Pattern)
public class CauHinhQuan {
private static CauHinhQuan instance;
String tenQuan;
String diaChi;
// Private constructor - không cho tạo từ bên ngoài
private CauHinhQuan() {
this.tenQuan = "Java Café";
this.diaChi = "123 Lê Lợi";
System.out.println("🔒 Khởi tạo cấu hình quán");
}
// Method public để lấy instance duy nhất
public static CauHinhQuan getInstance() {
if (instance == null) {
instance = new CauHinhQuan();
}
return instance;
}
public void hienThi() {
System.out.println("🏪 " + tenQuan + " - " + diaChi);
}
public static void main(String[] args) {
// CauHinhQuan c1 = new CauHinhQuan(); // ❌ Lỗi! Private
CauHinhQuan c1 = CauHinhQuan.getInstance();
CauHinhQuan c2 = CauHinhQuan.getInstance(); // Cùng 1 instance
System.out.println("c1 == c2? " + (c1 == c2)); // true
c1.hienThi();
}
}
🔥 Thực Hành Trong Quán
Bài Tập 1: Class NhanVien
public class NhanVien {
String ten;
String viTri;
int luong;
public NhanVien(String ten, String viTri, int luong) {
this.ten = ten;
this.viTri = viTri;
this.luong = luong;
}
public NhanVien(String ten, String viTri) {
this(ten, viTri, 5000000); // Lương mặc định
}
public void hienThi() {
System.out.printf("👤 %s - %s: %,d VND%n", ten, viTri, luong);
}
public void tangLuong(int soTien) {
luong += soTien;
System.out.println("⬆️ Tăng lương: +" + soTien);
}
public static void main(String[] args) {
NhanVien nv1 = new NhanVien("Minh", "Barista", 7000000);
NhanVien nv2 = new NhanVien("Lan", "Thu ngân");
nv1.hienThi();
nv2.hienThi();
System.out.println();
nv1.tangLuong(500000);
nv1.hienThi();
}
}
Bài Tập 2: Class ThucDon
public class ThucDon {
String[] tenMon;
int[] gia;
int soMon;
public ThucDon(int kichThuocToiDa) {
tenMon = new String[kichThuocToiDa];
gia = new int[kichThuocToiDa];
soMon = 0;
}
public void themMon(String ten, int giaMon) {
if (soMon < tenMon.length) {
tenMon[soMon] = ten;
gia[soMon] = giaMon;
soMon++;
System.out.println("✅ Đã thêm: " + ten);
} else {
System.out.println("❌ Thực đơn đầy!");
}
}
public void hienThi() {
System.out.println("\n☕ THỰC ĐƠN:");
for (int i = 0; i < soMon; i++) {
System.out.printf("%d. %-12s: %,d VND%n",
(i + 1), tenMon[i], gia[i]);
}
}
public static void main(String[] args) {
ThucDon menu = new ThucDon(5);
menu.themMon("Espresso", 45000);
menu.themMon("Latte", 50000);
menu.themMon("Mocha", 60000);
menu.hienThi();
}
}
⚠️ Lỗi Thường Gặp
Lỗi 1: Constructor Có Return Type
// ❌ SAI: Constructor không có return type
public void QuanCafe(String ten) { // ❌ Lỗi! Có void
this.ten = ten;
}
// ✅ ĐÚNG: Không có return type
public QuanCafe(String ten) { // ✅ OK
this.ten = ten;
}
Lỗi 2: Tên Constructor Sai
public class QuanCafe {
// ❌ SAI: Tên khác với class
public QuanCaPhe(String ten) {} // ❌ Lỗi!
// ✅ ĐÚNG: Cùng tên với class
public QuanCafe(String ten) {} // ✅ OK
}
Lỗi 3: Quên this
public class DoUong {
String ten;
int gia;
// ❌ SAI: Không dùng this
public DoUong(String ten, int gia) {
ten = ten; // ❌ Gán tham số cho chính nó!
gia = gia;
}
// ✅ ĐÚNG: Dùng this
public DoUong(String ten, int gia) {
this.ten = ten; // ✅ OK
this.gia = gia;
}
}
Lỗi 4: this() Không Ở Dòng Đầu
// ❌ SAI: this() phải ở dòng đầu tiên
public DoUong(String ten) {
System.out.println("Khởi tạo");
this(ten, 50000); // ❌ Lỗi! Không ở đầu
}
// ✅ ĐÚNG: this() ở dòng đầu
public DoUong(String ten) {
this(ten, 50000); // ✅ OK
System.out.println("Khởi tạo");
}
💡 Bí Quyết Của Barista
- Cùng tên với class: Constructor phải trùng tên
- Không return type: Kể cả void
- Dùng this: Phân biệt thuộc tính và tham số
- Overload hợp lý: 2-3 constructor cho linh hoạt
- this() đầu tiên: Gọi constructor khác ở dòng đầu
- Validation: Kiểm tra tham số trong constructor
🎓 Bạn Đã Học Được
- ✅ Constructor = Hàm khởi tạo object
- ✅ Cùng tên class, không return type
- ✅ Tự động chạy khi
new - ✅ Overload constructor
- ✅ this = Tham chiếu object hiện tại
- ✅ this() = Gọi constructor khác
- ✅ Default constructor vs Custom constructor
- ✅ Ứng dụng: Khởi tạo giá trị, validation
☕ Món Tiếp Theo
Đã biết constructor! Giờ học về recursion:
💡 Lời Khuyên Cuối: Constructor như lễ khai trương quán - chuẩn bị kỹ càng để hoạt động trơn tru!