Thừa Kế (Inheritance): 'Cha truyền con nối' trong code Java
Java – OOP

Thừa Kế (Inheritance): 'Cha truyền con nối' trong code Java

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

47 Lượt

Inheritance

Chào các Gen Z, Creyt đây! Hôm nay chúng ta sẽ "bóc tách" một khái niệm mà nghe tên thôi đã thấy mùi gia phả rồi: Inheritance hay còn gọi là Thừa Kế trong Java OOP. Tưởng tượng thế này: ông bà mình có gen thông minh, rồi truyền cho bố mẹ, bố mẹ lại truyền cho mình. Mình không cần phải tự "phát minh" lại bộ gen đó, mà mình kế thừa nó, và có thể phát triển thêm những cái mới của riêng mình. Trong lập trình, Inheritance y chang vậy, nhưng là với code!

Vậy, Inheritance là gì và để làm gì? Đơn giản là cơ chế cho phép một class (gọi là child class hay subclass) kế thừa các thuộc tính (fields) và phương thức (methods) từ một class khác (gọi là parent class hay superclass).

Mục đích cốt lõi?

  • Tái sử dụng code (Code Reusability): Tránh việc viết lại những đoạn code giống nhau. Giống như bạn không cần phải tự tạo lại cái bánh xe khi đã có người tạo ra rồi ấy.
  • Mở rộng chức năng (Extend Functionality): Class con có thể thêm các thuộc tính, phương thức mới hoặc thay đổi cách hoạt động của phương thức từ class cha (override).
  • Tạo ra mối quan hệ "is-a": Một Car là một Vehicle. Một Dog là một Animal. Mối quan hệ này cực kỳ quan trọng để thiết kế hệ thống có cấu trúc và dễ hiểu.

Trong Java, để hiện thực hóa Inheritance, chúng ta dùng từ khóa extends.

// Class cha (Superclass) - Nền tảng chung
class Vehicle {
    String brand;

    public Vehicle(String brand) {
        this.brand = brand;
    }

    public void start() {
        System.out.println(brand + " đang khởi động...");
    }

    public void stop() {
        System.out.println(brand + " đang dừng lại.");
    }
}

// Class con (Subclass) - Kế thừa từ Vehicle
class Car extends Vehicle {
    int numberOfDoors;

    public Car(String brand, int numberOfDoors) {
        // Gọi constructor của class cha để khởi tạo brand
        super(brand);
        this.numberOfDoors = numberOfDoors;
    }

    // Phương thức riêng của Car
    public void drive() {
        System.out.println(brand + " đang chạy trên đường với " + numberOfDoors + " cửa.");
    }

    // Ghi đè (Override) phương thức từ class cha
    @Override
    public void start() {
        System.out.println(brand + " khởi động bằng cách vặn chìa khóa.");
    }
}

// Một class con khác, kế thừa từ Car
class ElectricCar extends Car {
    int batteryCapacity; // dung lượng pin

    public ElectricCar(String brand, int numberOfDoors, int batteryCapacity) {
        super(brand, numberOfDoors); // Gọi constructor của Car
        this.batteryCapacity = batteryCapacity;
    }

    // Phương thức riêng của ElectricCar
    public void charge() {
        System.out.println(brand + " đang sạc pin với dung lượng " + batteryCapacity + " kWh.");
    }

    // Ghi đè phương thức start lần nữa
    @Override
    public void start() {
        System.out.println(brand + " khởi động im lặng bằng nút bấm.");
    }

    public static void main(String[] args) {
        Car myCar = new Car("Toyota", 4);
        myCar.start(); // Sẽ gọi phương thức start của Car
        myCar.drive();
        myCar.stop();

        System.out.println("---");

        ElectricCar myElectricCar = new ElectricCar("Tesla", 4, 100);
        myElectricCar.start(); // Sẽ gọi phương thức start của ElectricCar
        myElectricCar.drive(); // Kế thừa từ Car
        myElectricCar.charge(); // Phương thức riêng
        myElectricCar.stop(); // Kế thừa từ Vehicle
    }
}

Trong ví dụ trên:

  • Car kế thừa Vehicle: Carbrand, start(), stop() và thêm numberOfDoors, drive(), đồng thời ghi đè start().
  • ElectricCar kế thừa Car: ElectricCar có tất cả của VehicleCar, và thêm batteryCapacity, charge(), đồng thời ghi đè start() một lần nữa.
  • super() được dùng để gọi constructor của class cha.
  • @Override là một annotation (chú thích) giúp trình biên dịch kiểm tra xem bạn có thực sự ghi đè một phương thức từ class cha hay không. Nó không bắt buộc nhưng cực kỳ nên dùng để tránh lỗi.

Từ góc nhìn học thuật mà vẫn dễ nuốt, Inheritance không chỉ là chuyện "cha truyền con nối" đơn thuần. Nó là một trong bốn trụ cột của Lập trình Hướng đối tượng (OOP), cùng với Encapsulation, Abstraction và Polymorphism.

Khi bạn dùng Inheritance, bạn đang xây dựng một hệ thống phân cấp (hierarchy) các lớp. Điều này cho phép bạn xử lý các đối tượng con như thể chúng là đối tượng cha của chúng. Đây chính là gốc rễ của Polymorphism (Đa hình) – khả năng một biến tham chiếu có thể chứa nhiều kiểu đối tượng khác nhau và gọi phương thức tương ứng với kiểu đối tượng thực tế. Ví dụ, bạn có thể tạo một mảng Vehicle[] và chứa cả Car lẫn ElectricCar trong đó, sau đó gọi start() cho từng chiếc mà không cần biết chính xác loại xe là gì.

Illustration

Một điểm quan trọng nữa là trong Java, một class chỉ có thể kế thừa trực tiếp từ một class cha (single inheritance). Điều này giúp tránh các vấn đề phức tạp như "Diamond Problem" thường gặp ở các ngôn ngữ hỗ trợ đa kế thừa. Tuy nhiên, một class có thể triển khai nhiều interface, đây là cách Java giải quyết nhu cầu về khả năng đa kế thừa chức năng.

Tất cả các class trong Java, nếu không khai báo extends rõ ràng, đều mặc định kế thừa từ class Object. Object là ông tổ của mọi class, cung cấp các phương thức cơ bản như equals(), hashCode(), toString().

Để dùng Inheritance hiệu quả như một pro, nhớ mấy mẹo này:

  • Kiểm tra mối quan hệ 'is-a': Luôn tự hỏi 'Class con có PHẢI LÀ một Class cha không?'. Nếu CarVehicle thì OK. Nếu EngineCar thì sai bét, đó là mối quan hệ 'has-a' (composition) chứ không phải 'is-a'.
  • Ưu tiên Composition hơn Inheritance (Favor Composition over Inheritance): Nghe hơi ngược đời nhưng rất quan trọng. Khi bạn cần tái sử dụng code nhưng không có mối quan hệ 'is-a' rõ ràng, hãy dùng Composition (một class chứa một đối tượng của class khác) thay vì Inheritance để tránh sự phụ thuộc chặt chẽ không cần thiết.
  • Giữ hệ thống phân cấp nông (Keep Hierarchy Shallow): Đừng tạo ra quá nhiều tầng kế thừa (ví dụ: A -> B -> C -> D -> E...). Càng sâu, code càng khó hiểu, khó bảo trì và dễ gây ra "sự thay đổi giật cục" (fragile base class problem).
  • Sử dụng final một cách khôn ngoan: Nếu bạn không muốn một class bị kế thừa, hãy khai báo nó là final class. Nếu không muốn một phương thức bị ghi đè, khai báo nó là final method. Điều này giúp kiểm soát kiến trúc và tránh những thay đổi không mong muốn.

Inheritance không chỉ là lý thuyết suông đâu, nó được dùng khắp nơi trong các ứng dụng bạn dùng hàng ngày:

  • Các thư viện GUI (Graphical User Interface): Ví dụ như Swing hay JavaFX. Bạn có JButton, JTextField đều kế thừa từ JComponent (Swing) hoặc Node (JavaFX), rồi từ đó kế thừa các hành vi chung như hiển thị, xử lý sự kiện.
  • Framework như Spring: Các Controller trong Spring MVC thường kế thừa một class cơ sở nào đó để có các chức năng chung như xử lý lỗi, xác thực.
  • Hệ thống quản lý file: Các loại file (TextFile, ImageFile, AudioFile) đều có thể kế thừa từ một class File chung, có các phương thức như open(), close(), nhưng mỗi loại file sẽ triển khai khác nhau.
  • Các class Collection trong Java: ArrayList, LinkedList đều triển khai (implement) List interface, và có thể nói là có mối quan hệ tương tự như kế thừa về mặt hành vi, mặc dù không dùng extends trực tiếp với nhau mà với một AbstractList chung. (Đây là một ví dụ về kết hợp giữa inheritance và interface).
  • Các lớp Exception: IOException, SQLException đều kế thừa từ class Exception chung, cho phép xử lý lỗi một cách có hệ thống.

Với kinh nghiệm của Creyt, tôi đã dùng Inheritance trong vô số dự án.

  • Khi nào nên dùng? Khi bạn có một tập hợp các đối tượng có chung các đặc điểm và hành vi cơ bản, nhưng cũng có những đặc điểm và hành vi riêng biệt. Ví dụ, xây dựng một game có nhiều loại kẻ thù (Enemy), nhưng tất cả đều có health, attack(), move(). Bạn tạo class Enemy chung, rồi Goblin extends Enemy, Orc extends Enemy, mỗi con có cách attack()move() riêng. Đây là lúc nó tỏa sáng.
  • Thử nghiệm đã từng: Tôi từng thử xây dựng một hệ thống quản lý tài khoản ngân hàng. Class Account làm cha, rồi CheckingAccountSavingsAccount làm con. Ban đầu rất ngon, tái sử dụng deposit(), withdraw(). Nhưng khi yêu cầu phức tạp hơn, ví dụ InterestBearingAccount (tài khoản có lãi suất), tôi lại muốn nó kế thừa cả CheckingAccountSavingsAccount để có lãi suất. Java không cho phép đa kế thừa class, nên tôi phải chuyển sang dùng interface và composition để giải quyết.
  • Khi nào nên tránh? Khi mối quan hệ 'is-a' không rõ ràng hoặc khi bạn thấy mình phải ghi đè quá nhiều phương thức của class cha để làm cho class con hoạt động đúng ý. Đó là dấu hiệu của việc thiết kế không tối ưu, và có thể bạn nên xem xét lại bằng cách sử dụng interface hoặc composition.

Nhớ nhé, Inheritance là một công cụ mạnh mẽ, nhưng như mọi công cụ khác, nó cần được sử dụng đúng chỗ, đúng lúc. Đừng lạm dụng nó, hãy luôn tư duy về sự linh hoạt và khả năng bảo trì của code sau này. Đó mới là đẳng cấp của một coder thực thụ!

Thuộc Series: Java – OOP

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!