Chuyển tới nội dung chính

🏗️ 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

  1. Cùng tên với class
  2. Không có return type (kể cả void)
  3. Tự động gọi khi tạo object với new
  4. Có thể overload (nhiều constructor)
  5. 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

  1. Cùng tên với class: Constructor phải trùng tên
  2. Không return type: Kể cả void
  3. Dùng this: Phân biệt thuộc tính và tham số
  4. Overload hợp lý: 2-3 constructor cho linh hoạt
  5. this() đầu tiên: Gọi constructor khác ở dòng đầu
  6. 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:

👉 Recursion - Đệ Quy


💡 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!