Backend

99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

SSite Admin
10 tháng 08, 2026 6 phút đọc 87 lượt xem
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Trụ cột thứ hai của OOP lên sàn: kế thừa (inheritance). Kế thừa cho phép gom phần chung vào lớp cha, để các lớp con extends — nhận trọn thuộc tính rồi thêm của riêng và ghi đè thứ muốn làm khác. Kèm một điều quan trọng không kém: khi nào đừng kế thừa.

Sketchnote Ngày 14: kế thừa trong Java — extends, super, override vs overload, protected và is-a vs has-a

extends — nhận thuộc tính

public class NhanVien {                       // lớp CHA (superclass)
    protected String ten;                     // protected: con nhìn thấy — Ngày 13!
    protected double luongCoBan;

    public NhanVien(String ten, double luongCoBan) {
        this.ten = ten;
        this.luongCoBan = luongCoBan;
    }

    public double tinhLuong() { return luongCoBan; }
    public String gioiThieu() { return "Tôi là " + ten; }
}

public class LapTrinhVien extends NhanVien {  // lớp CON — "LÀ MỘT" nhân viên
    private String ngonNgu;                   // + field RIÊNG

    public LapTrinhVien(String ten, double luong, String ngonNgu) {
        super(ten, luong);                    // gọi constructor CHA — dòng đầu tiên!
        this.ngonNgu = ngonNgu;
    }

    public void deployThuSau() { }            // + method RIÊNG
}

LapTrinhVien dev = new LapTrinhVien("An", 20_000_000, "Java");
dev.gioiThieu();      // dùng CHÙA method của cha — không viết lại dòng nào
dev.tinhLuong();      // 20_000_000
  • Lớp con nhận toàn bộ field + method (trừ constructor) của cha, rồi cứ thế cơi nới: thêm ngonNgu, thêm deployThuSau.

  • protected — mảnh ghép Ngày 13 còn nợ: như private nhưng mở cho lớp con (và cùng package). Cân nhắc: private + getter vẫn thường là lựa chọn kín đáo hơn.

  • Java là đơn kế thừa: mỗi lớp chỉ extends một cha — không có chuyện hai cha như C++. (Muốn "nhiều vai" thì chờ interface — Ngày 17.)

  • Không extends ai cả? Bạn vẫn đang kế thừa: mọi lớp đều ngầm nối về Object — tổ tiên chung cấp toString, equals, hashCode (đại tiệc Ngày 24).

Override — viết lại

public class LapTrinhVien extends NhanVien {
    private double phuCapDuAn;

    @Override                                  // LUÔN dán nhãn này khi ghi đè!
    public double tinhLuong() {
        return super.tinhLuong() + phuCapDuAn; // super. = mượn bản CỦA CHA rồi cộng thêm
    }

    @Override
    public String gioiThieu() {
        return super.gioiThieu() + " — lập trình viên " + ngonNgu;
    }
}

// @Override giúp compiler BẮT LỖI GÕ NHẦM://   tinhLuog()  → không có @Override: thành method MỚI, bug im lặng!//   tinhLuog() + @Override → lỗi biên dịch ngay: "does not override"

// OVERRIDE ≠ OVERLOAD (Ngày 10)://   override: lớp con thay RUỘT method cha — CÙNG chữ ký//   overload: cùng tên KHÁC tham số — trong cùng một lớp cũng được
  • Override = giữ nguyên chữ ký, thay ruột: tinhLuong của dev khác kế toán là chuyện nghiệp vụ tự nhiên.

  • super. cho phép mượn bản gốc rồi gia cố — mẫu "lấy của cha cộng thêm" phổ biến hơn nhiều so với đập đi xây lại.

  • @Override là dây an toàn miễn phí: gõ nhầm tên/chữ ký là compiler tuýt còi ngay thay vì để một method "song sinh" âm thầm ra đời — dán nó mọi lúc, không ngoại lệ.

  • Phân biệt dứt điểm cặp gây nhiễu số một: override — cùng chữ ký, quan hệ cha-con; overload (Ngày 10) — cùng tên khác tham số. Phỏng vấn hỏi hoài không chán.

super(...) — cha khởi tạo trước

// Thứ tự khởi tạo khi new LapTrinhVien(...): CHA TRƯỚC, CON SAU////   new LapTrinhVien("An", 20tr, "Java")//     → constructor LapTrinhVien chạy super(ten, luong)   ← BẮT BUỘC dòng đầu//       → constructor NhanVien khởi tạo ten, luongCoBan   ← phần "cha" xong trước//     → phần thân còn lại: this.ngonNgu = ngonNgu         ← rồi mới tới phần "con"//// Không viết super(...)? Compiler tự chèn super() KHÔNG THAM SỐ.// Nhưng nếu cha KHÔNG CÓ constructor không tham số (quà đã bị rút — Ngày 12!)// → lỗi biên dịch: bạn PHẢI gọi super(...) tường minh.//// Ghép nối quen thuộc: this(...) hoặc super(...) — chỉ MỘT trong hai, luôn dòng đầu.

Logic rất đời: muốn dựng phần "lập trình viên" thì phần "nhân viên" bên dưới phải xong trước — nền trước, nhà sau. Toàn bộ luật Ngày 12 tái xuất nguyên bộ: quà default constructor, dòng đầu tiên, và dây chuyền ủy thác — chỉ thêm một tầng cha-con.

Khi nào ĐỪNG kế thừa: is-a vs has-a

// "LÀ MỘT" (is-a) — phép thử trước khi extends://   LapTrinhVien LÀ MỘT NhanVien?  ✓ hợp lý → extends//   Oto LÀ MỘT DongCo?             ✗ Ô tô CÓ động cơ — không PHẢI động cơ!

public class Oto {
    private DongCo dongCo;        // ✓ COMPOSITION — "CÓ MỘT" (has-a): field, không extends
    private BanhXe[] banhXe;
}

// Vì sao lạm dụng kế thừa nguy hiểm?//  - Con dính CHẶT vào ruột cha: cha đổi nội thất, con sập theo (fragile base class)//  - Java CHỈ CHO extends MỘT lớp — dùng "vé duy nhất" cho đúng quan hệ is-a thật//  - Móc nối hành vi linh hoạt → composition + interface (Ngày 17) thường đúng hơn// Châm ngôn khắc cốt: "favor composition over inheritance"
  • Phép thử một câu trước mỗi extends: đọc to "X là một Y" — nghe gượng là sai quan hệ, chuyển sang has-a (field) ngay.

  • Kế thừa là khớp nối cứng nhất giữa hai lớp: con thấy protected, phụ thuộc thứ tự khởi tạo, vỡ theo mọi thay đổi của cha. Sức mạnh lớn — giá cũng lớn.

  • Kinh nghiệm thực chiến: cây kế thừa nông (1–2 tầng) là lành mạnh; cây 5 tầng A extends B extends C... là mê cung debug. Nghi ngờ thì composition trước.

Bài tập nhỏ

  • Dựng NhanVien với hai con LapTrinhVien (phụ cấp dự án) và KeToan (thưởng quyết toán) — mỗi con override tinhLuong một kiểu, có dùng super..

  • Cố tình bỏ super(...) trong constructor con khi cha không có bản không tham số — đọc kỹ lỗi, rồi sửa.

  • Gõ nhầm tinhLuog không có @Override — quan sát bug im lặng; thêm @Override và xem compiler cứu bạn.

  • Xét Oto/DongCo, HinhVuong/HinhChuNhat, ArrayList/List — quan hệ nào là is-a thật? (Câu giữa gài bẫy đấy — thử override setChieuDai của hình vuông xem có ổn không.)

Kết luận

Trụ cột thứ hai đã đứng: extends trao gia tài từ cha xuống con, super gọi ngược lên cả ở constructor lẫn method, @Override gác cổng chữ ký, protected hé cửa đúng mức — và phép thử is-a giữ bạn khỏi lạm dụng. Nhưng kho báu thật sự của kế thừa chưa lộ diện: khi NhanVien nv = new LapTrinhVien(...) — biến kiểu cha cầm đối tượng con — thì nv.tinhLuong() chạy bản của ai? Câu trả lời là đa hình (polymorphism) — trụ cột đẹp nhất của OOP, trọn vẹn ở Ngày 15. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Java — Ngày 58: Optional

Optional là công cụ cho kiểu trả về — không field, không tham số, không bọc collection; chuỗi map/flatMap/filter thay kim tự tháp if; orElse luôn tính tham số còn orElseGet lười, orElseThrow cho “không có là lỗi” — và các anti-pattern isPresent + get, orElse(null).

23 thg 9, 20269 phút9
99 Ngày Spring — Ngày 58: Spring Boot Actuator

Boot 3 mặc định chỉ lộ health; bản đồ endpoint an toàn và nguy hiểm; HealthIndicator tự viết với liveness tách khỏi readiness cho Kubernetes, info từ build và git, metrics Micrometer với tag hữu hạn — và ba lớp khóa: cổng riêng, SecurityFilterChain với EndpointRequest, exclude env/heapdump/threaddump.

23 thg 9, 202610 phút5
So sánh EasyExcel và Apache Fesod: khác biệt thật nằm ở đâu?

Fesod là hậu duệ của EasyExcel, cùng engine SAX/SXSSF. Khác biệt kiểm chứng được: vòng đời dự án, POI 5.2.5 vs 5.5.1, các API mới (numRows, csv(), headerMergeStrategy) và độ bền với file xấu — không phải hiệu năng gấp nhiều lần.

23 thg 9, 20268 phút1