🔁 Ghi Đè Phương Thức - Method Overriding
Chào mừng đến với bài học về Method Overriding - tùy chỉnh công thức riêng cho chi nhánh Café!
🎯 Món Ăn Hôm Nay
Tưởng tượng chi nhánh muốn tùy chỉnh công thức:
- Quán chính: pha() theo cách A → Method gốc
- Chi nhánh: pha() theo cách B → Override method
- Cùng tên, khác cách làm → Linh hoạt
Đó chính là Method Overriding - ghi đè method của class cha!
🔁 Method Overriding Là Gì?
Method Overriding = Ghi đè (class con thay đổi cách thực hiện method của cha)
// Class cha
public class DoUong {
public void pha() {
System.out.println("☕ Pha theo công thức chuẩn");
}
}
// Class con - Override method pha()
public class Latte extends DoUong {
@Override
public void pha() {
System.out.println("☕ Pha Latte:");
System.out.println(" 1. Espresso");
System.out.println(" 2. Sữa nóng");
System.out.println(" 3. Foam");
}
}
☕ Ẩn Dụ Café:
- Method cha = Công thức gốc từ quán chính
- Override = Chi nhánh tùy chỉnh công thức
- @Override = Nhãn "đã tùy chỉnh"
- super.method() = Tham khảo công thức gốc
- Polymorphism = Linh hoạt gọi đúng phiên bản
📋 Quy Tắc Override
Override hợp lệ khi:
- ✅ Cùng tên method
- ✅ Cùng tham số (signature)
- ✅ Cùng hoặc rộng hơn access modifier
- ✅ Cùng hoặc hẹp hơn return type (covariant)
- ✅ Không thể override: final, static, private
// ✅ ĐÚNG: Override hợp lệ
public class Cha {
public void pha(String mon) {
System.out.println("Pha " + mon);
}
}
public class Con extends Cha {
@Override
public void pha(String mon) { // ✅ Cùng signature
System.out.println("Pha " + mon + " đặc biệt");
}
}
// ❌ SAI: Khác signature
public class Con2 extends Cha {
@Override
public void pha(String mon, int soLuong) { // ❌ Lỗi! Khác tham số
// Đây là overload, không phải override
}
}
👨🍳 Câu Chuyện Trong Quán
Tình huống 1: Override Đơn Giản
public class NhanVien {
protected String ten;
public NhanVien(String ten) {
this.ten = ten;
}
public void lamViec() {
System.out.println("👤 " + ten + " đang làm việc");
}
}
public class Barista extends NhanVien {
public Barista(String ten) {
super(ten);
}
@Override
public void lamViec() {
System.out.println("☕ " + ten + " đang pha cà phê");
}
}
public class ThuNgan extends NhanVien {
public ThuNgan(String ten) {
super(ten);
}
@Override
public void lamViec() {
System.out.println("💰 " + ten + " đang thu ngân");
}
}
// Sử dụng
public class TestOverride {
public static void main(String[] args) {
NhanVien nv1 = new Barista("Minh");
NhanVien nv2 = new ThuNgan("Lan");
nv1.lamViec(); // Gọi phiên bản Barista
nv2.lamViec(); // Gọi phiên bản ThuNgan
}
}
Output:
☕ Minh đang pha cà phê
💰 Lan đang thu ngân
Tình huống 2: super - Gọi Method Cha
public class DoUong {
protected String ten;
protected int gia;
public DoUong(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}
public void hienThi() {
System.out.printf("☕ %s: %,d VND%n", ten, gia);
}
}
public class DoUongDacBiet extends DoUong {
private String ghiChu;
public DoUongDacBiet(String ten, int gia, String ghiChu) {
super(ten, gia);
this.ghiChu = ghiChu;
}
@Override
public void hienThi() {
super.hienThi(); // Gọi method cha trước
System.out.println(" 🌟 " + ghiChu);
}
}
// Sử dụng
public class TestSuper {
public static void main(String[] args) {
DoUongDacBiet latte = new DoUongDacBiet(
"Latte Lavender", 65000, "Hương lavender thơm ngát"
);
latte.hienThi();
}
}
Output:
☕ Latte Lavender: 65,000 VND
🌟 Hương lavender thơm ngát
📝 Công Thức Nấu (Code Examples)
Ví Dụ 1: Polymorphism Với Override
public class SanPham {
protected String ten;
protected int gia;
public SanPham(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}
public int tinhGiamGia() {
return 0; // Mặc định không giảm
}
public int tinhThanhToan() {
return gia - tinhGiamGia();
}
public void hienThi() {
System.out.printf("%-15s: %,d VND%n", ten, tinhThanhToan());
}
}
public class SanPhamVIP extends SanPham {
public SanPhamVIP(String ten, int gia) {
super(ten, gia);
}
@Override
public int tinhGiamGia() {
return gia * 20 / 100; // Giảm 20%
}
}
public class SanPhamKhuyenMai extends SanPham {
private int soTienGiam;
public SanPhamKhuyenMai(String ten, int gia, int soTienGiam) {
super(ten, gia);
this.soTienGiam = soTienGiam;
}
@Override
public int tinhGiamGia() {
return soTienGiam;
}
}
// Sử dụng
public class TestPolymorphism {
public static void main(String[] args) {
SanPham[] danhSach = {
new SanPham("Espresso", 45000),
new SanPhamVIP("Latte VIP", 50000),
new SanPhamKhuyenMai("Mocha KM", 60000, 15000)
};
System.out.println("💰 BẢNG GIÁ:\n");
for (SanPham sp : danhSach) {
sp.hienThi(); // Tự động gọi đúng phiên bản
}
}
}
Output:
💰 BẢNG GIÁ:
Espresso : 45,000 VND
Latte VIP : 40,000 VND
Mocha KM : 45,000 VND
Ví Dụ 2: Covariant Return Type
public class DoUong {
protected String ten;
public DoUong(String ten) {
this.ten = ten;
}
public DoUong taoMoi() {
return new DoUong(ten);
}
}
public class CaPhe extends DoUong {
private String loaiHat;
public CaPhe(String ten, String loaiHat) {
super(ten);
this.loaiHat = loaiHat;
}
// Covariant return type: Trả về CaPhe thay vì DoUong
@Override
public CaPhe taoMoi() {
return new CaPhe(ten, loaiHat);
}
}
// Sử dụng
public class TestCovariant {
public static void main(String[] args) {
CaPhe latte = new CaPhe("Latte", "Arabica");
CaPhe latteClone = latte.taoMoi(); // Không cần cast
System.out.println("✅ Đã sao chép: " + latteClone.ten);
}
}
Ví Dụ 3: Override toString()
public class DonHang {
private String maDon;
private String tenKhach;
private int tongTien;
public DonHang(String maDon, String tenKhach, int tongTien) {
this.maDon = maDon;
this.tenKhach = tenKhach;
this.tongTien = tongTien;
}
@Override
public String toString() {
return String.format("🧾 Đơn %s | %s | %,d VND",
maDon, tenKhach, tongTien);
}
}
// Sử dụng
public class TestToString {
public static void main(String[] args) {
DonHang don = new DonHang("DH001", "Anh Minh", 150000);
System.out.println(don); // Tự động gọi toString()
}
}
Output:
🧾 Đơn DH001 | Anh Minh | 150,000 VND
Ví Dụ 4: Override equals()
public class MonCaPhe {
private String ten;
private int gia;
public MonCaPhe(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MonCaPhe other = (MonCaPhe) obj;
return gia == other.gia && ten.equals(other.ten);
}
@Override
public String toString() {
return ten + " - " + gia + "đ";
}
}
// Sử dụng
public class TestEquals {
public static void main(String[] args) {
MonCaPhe m1 = new MonCaPhe("Latte", 50000);
MonCaPhe m2 = new MonCaPhe("Latte", 50000);
MonCaPhe m3 = new MonCaPhe("Mocha", 60000);
System.out.println("m1 == m2? " + (m1 == m2)); // false (khác tham chiếu)
System.out.println("m1.equals(m2)? " + m1.equals(m2)); // true (cùng nội dung)
System.out.println("m1.equals(m3)? " + m1.equals(m3)); // false
}
}
Output:
m1 == m2? false
m1.equals(m2)? true
m1.equals(m3)? false
Ví Dụ 5: final Method - Không Override Được
public class QuanCafe {
protected String ten;
public QuanCafe(String ten) {
this.ten = ten;
}
// final method: Không thể override
public final void moC ua() {
System.out.println("🏪 " + ten + " mở cửa lúc 7:00");
}
// Method bình thường: Override được
public void dongCua() {
System.out.println("🔒 " + ten + " đóng cửa lúc 22:00");
}
}
public class QuanCafeChiNhanh extends QuanCafe {
public QuanCafeChiNhanh(String ten) {
super(ten);
}
// ❌ Không thể override moCua() vì nó là final
// @Override
// public void moCua() {} // Lỗi!
// ✅ Override dongCua() được
@Override
public void dongCua() {
System.out.println("🔒 " + ten + " đóng cửa lúc 23:00");
}
}
Ví Dụ 6: Static Method - Không Override
public class Helper {
public static void inThongBao() {
System.out.println("📢 Thông báo từ class cha");
}
}
public class HelperMoi extends Helper {
// Đây KHÔNG phải override, mà là method hiding
public static void inThongBao() {
System.out.println("📢 Thông báo từ class con");
}
}
// Sử dụng
public class TestStatic {
public static void main(String[] args) {
Helper.inThongBao(); // Gọi của cha
HelperMoi.inThongBao(); // Gọi của con
Helper h = new HelperMoi();
h.inThongBao(); // Vẫn gọi của cha! (không phải override)
}
}
Output:
📢 Thông báo từ class cha
📢 Thông báo từ class con
📢 Thông báo từ class cha
🔥 Thực Hành Trong Quán
Bài Tập 1: Hệ Thống Thanh Toán
public abstract class PhuongThucThanhToan {
protected int soTien;
public PhuongThucThanhToan(int soTien) {
this.soTien = soTien;
}
public abstract void thanhToan();
public void xacNhan() {
System.out.printf("✅ Đã thanh toán: %,d VND%n", soTien);
}
}
public class ThanhToanTienMat extends PhuongThucThanhToan {
public ThanhToanTienMat(int soTien) {
super(soTien);
}
@Override
public void thanhToan() {
System.out.println("💵 Thanh toán tiền mặt:");
System.out.printf(" Số tiền: %,d VND%n", soTien);
xacNhan();
}
}
public class ThanhToanThe extends PhuongThucThanhToan {
private String soThe;
public ThanhToanThe(int soTien, String soThe) {
super(soTien);
this.soThe = soThe;
}
@Override
public void thanhToan() {
System.out.println("💳 Thanh toán qua thẻ:");
System.out.println(" Số thẻ: **** **** **** " + soThe.substring(12));
System.out.printf(" Số tiền: %,d VND%n", soTien);
xacNhan();
}
}
// Test
public class TestThanhToan {
public static void main(String[] args) {
PhuongThucThanhToan tt1 = new ThanhToanTienMat(100000);
PhuongThucThanhToan tt2 = new ThanhToanThe(150000, "1234567890123456");
tt1.thanhToan();
System.out.println();
tt2.thanhToan();
}
}
Bài Tập 2: Tính Diện Tích Hình Học
public abstract class HinhHoc {
protected String ten;
public HinhHoc(String ten) {
this.ten = ten;
}
public abstract double tinhDienTich();
public void hienThi() {
System.out.printf("%s: %.2f m²%n", ten, tinhDienTich());
}
}
public class HinhVuong extends HinhHoc {
private double canh;
public HinhVuong(double canh) {
super("Hình vuông");
this.canh = canh;
}
@Override
public double tinhDienTich() {
return canh * canh;
}
}
public class HinhTron extends HinhHoc {
private double banKinh;
public HinhTron(double banKinh) {
super("Hình tròn");
this.banKinh = banKinh;
}
@Override
public double tinhDienTich() {
return Math.PI * banKinh * banKinh;
}
}
// Test
public class TestHinhHoc {
public static void main(String[] args) {
HinhHoc[] cacHinh = {
new HinhVuong(5),
new HinhTron(3)
};
System.out.println("📐 DIỆN TÍCH:\n");
for (HinhHoc h : cacHinh) {
h.hienThi();
}
}
}
⚠️ Lỗi Thường Gặp
Lỗi 1: Khác Signature
// ❌ SAI: Khác tham số
public class Cha {
public void pha(String mon) {}
}
public class Con extends Cha {
@Override
public void pha(String mon, int soLuong) {} // ❌ Lỗi! Overload, không phải override
}
Lỗi 2: Access Modifier Hẹp Hơn
// ❌ SAI: Access modifier hẹp hơn
public class Cha {
public void pha() {}
}
public class Con extends Cha {
@Override
protected void pha() {} // ❌ Lỗi! Hẹp hơn public
}
Lỗi 3: Override Static Method
// ❌ SAI: Không override được static
public class Cha {
public static void helper() {}
}
public class Con extends Cha {
@Override // ❌ Lỗi! Static không override
public static void helper() {}
}
Lỗi 4: Override Final Method
// ❌ SAI: Không override được final
public class Cha {
public final void moC ua() {}
}
public class Con extends Cha {
@Override
public void moCua() {} // ❌ Lỗi! final không override
}
💡 Bí Quyết Của Barista
- @Override luôn: Dùng @Override để compiler kiểm tra
- super.method(): Gọi method cha khi cần
- Cùng signature: Tên, tham số, return type
- Access rộng hơn: public ≥ protected ≥ default
- final/static: Không override được
- Polymorphism: Tận dụng override cho linh hoạt
🎓 Bạn Đã Học Được
- ✅ Override = Ghi đè method của class cha
- ✅ Cùng tên, cùng signature
- ✅ @Override annotation
- ✅ super.method() = Gọi method cha
- ✅ Không override: final, static, private
- ✅ Access modifier: Rộng hơn hoặc bằng
- ✅ Covariant return type
- ✅ Polymorphism với override
☕ Món Tiếp Theo
Đã biết override! Giờ học về interface:
💡 Lời Khuyên Cuối: Override như điều chỉnh công thức - giữ tên nhưng thay đổi cách làm cho phù hợp!