Extends trong Java: Áp dụng kế thừa qua các ví dụ thực tế

Thay vì viết lại những đoạn code giống nhau cho từng đối tượng, extends trong Java cho phép các lớp con kế thừa lại tài nguyên từ lớp cha. Điều này không chỉ giúp tái sử dụng mã nguồn tối đa mà còn thiết lập một hệ thống phân cấp logic, giúp việc quản lý và mở rộng dự án trở nên cực kỳ hiệu quả.

Đọc bài viết này để hiểu thêm về:

  • Cú pháp extends và khái niệm cơ bản của kế thừa trong Java 
  • Cách hoạt động của extends (thông qua các ví dụ thực tế)
  • Nguyên tắc đơn kế thừa & quan hệ IS-A
  • So sánh cách sử dụng extends vs implements trong Java
  • Các câu hỏi thường gặp về extends trong Java

Cú pháp extends trong Java

Trong Java, từ khóa extends được sử dụng để thiết lập mối quan hệ giữa một lớp mới (lớp con) và một lớp đã tồn tại (lớp cha).

Cú pháp extends trong Java cơ bản

class SubClass extends SuperClass {
   // Các thuộc tính và phương thức bổ sung
}

Khái niệm cơ bản

Để làm việc hiệu quả với kế thừa, bạn cần nắm vững hai khái niệm sau:

  • Superclass (Lớp cha/Lớp cơ sở): Là lớp chứa các thuộc tính và phương thức chung, đóng vai trò là nguồn để các lớp khác kế thừa lại.
  • Subclass (Lớp con/Lớp dẫn xuất): Là lớp thực hiện kế thừa. Nó sở hữu tất cả các thành phần (có phạm vi truy cập phù hợp) của lớp cha và có thể mở rộng thêm các tính năng riêng biệt.

Cách hoạt động của extends trong Java

Sử dụng từ khóa extends không chỉ đơn thuần là kết nối hai lớp, mà nó còn mở ra những cơ chế xử lý linh hoạt sau:

Khả năng tái sử dụng (Reusability)

Lớp con sẽ tự động sở hữu các thuộc tính và phương thức có phạm vi truy cập public hoặc protected từ lớp cha.

Ví dụ: Nếu lớp cha NhanVien có thuộc tính tenluong, thì lớp LapTrinhVien khi kế thừa sẽ mặc nhiên có hai thuộc tính này mà không cần khai báo lại.

Ghi đè phương thức (Method Overriding)

Lớp con có thể định nghĩa lại một phương thức đã có ở lớp cha để phù hợp với đặc thù của nó bằng cách sử dụng annotation @Override.

Mục đích là giúp lớp con thay đổi hành vi của phương thức nhưng vẫn giữ nguyên tên và tham số.

Từ khóa super

Trong quá trình kế thừa, từ khóa super đóng vai trò là “cầu nối” ngược về lớp cha:

  • super(): Gọi constructor của lớp cha (phải là dòng đầu tiên trong constructor lớp con).
  • super.method(): Gọi một phương thức của lớp cha khi phương thức đó đã bị ghi đè ở lớp con.

Tính đa hình (Polymorphism)

Đây là tính năng quyền lực nhất của extends. Lớp cha có thể dùng làm kiểu dữ liệu tham chiếu đến đối tượng của lớp con.

Ví dụ: Bạn có thể tạo một danh sách NhanVien[]. Trong danh sách này, bạn có thể lưu trữ cả LapTrinhVien, QuanLyThietKe. Khi gọi hàm lamViec(), Java sẽ tự biết đối tượng đó là ai để thực hiện hành động riêng biệt của đối tượng đó.

Ví dụ sử dụng extends trong Java

Ví dụ 1: Kế thừa đơn giản giữa lớp Cha – Con

Hãy cùng xem cách một lớp Cho (Con) kế thừa từ lớp DongVat (Cha):

// Lớp cha (Superclass)
class DongVat {
    void an() {
        System.out.println("Đang ăn...");
    }
}

// Lớp con (Subclass) kế thừa từ DongVat
class Cho extends DongVat {
    void sua() {
        System.out.println("Gâu gâu!");
    }
}

public class ViDuKeThua {
    public static void main(String[] args) {
        Cho milu = new Cho();
        milu.an();  // Gọi phương thức kế thừa từ lớp cha
        milu.sua(); // Gọi phương thức của chính lớp con
    }
}

Giải thích tính kế thừa đoạn code trên:

  • Lớp Cho được khai báo bằng class Cho extends DongVat. Điều này tạo ra một liên kết kế thừa, cho phép đối tượng milu thuộc lớp Cho tự động có quyền gọi phương thức an() mà lẽ ra chỉ thuộc về lớp DongVat
  • Khi bạn gọi milu.an(), hệ thống sẽ kiểm tra lớp Cho → Nếu không thấy phương thức này, tự động tìm lên lớp cha DongVat → Sau đó thực thi phương thức tìm được
  • Đồng thời, milu vẫn có thể gọi phương thức riêng của mình là milu.sua()

Đây chính là cơ chế của tính kế thừa: Lớp con vừa tái sử dụng lại hành vi của lớp cha, vừa có thể bổ sung hành vi riêng.

Ví dụ 2: Hệ thống quản lý nhân viên đơn giản

Chúng ta có lớp cha là Employee và hai lớp con là DeveloperManager:

// 1. Lớp cha (Superclass)
class Employee {
    protected String name;
    protected double baseSalary;

    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    public void work() {
        System.out.println(name + " đang làm việc.");
    }

    public double calculateSalary() {
        return baseSalary;
    }
}

// 2. Lớp con Developer (Subclass)
class Developer extends Employee {
    private String programmingLanguage;

    public Developer(String name, double baseSalary, String programmingLanguage) {
        super(name, baseSalary); // Gọi constructor của lớp cha
        this.programmingLanguage = programmingLanguage;
    }

    @Override
    public void work() {
        System.out.println(name + " đang lập trình bằng ngôn ngữ " + programmingLanguage);
    }
}

// 3. Lớp con Manager (Subclass)
class Manager extends Employee {
    private int teamSize;

    public Manager(String name, double baseSalary, int teamSize) {
        super(name, baseSalary);
        this.teamSize = teamSize;
    }

    @Override
    public double calculateSalary() {
        return baseSalary + (teamSize * 500); // Thưởng thêm dựa trên số nhân viên quản lý
    }
}

Cách vận hành trong thực tế:

public class Main {
    public static void main(String[] args) {
        // Tính đa hình: Dùng kiểu Employee để quản lý các đối tượng con
        Employee dev = new Developer("Hoàng", 1500, "Java");
        Employee manager = new Manager("Lan", 2000, 5);

        dev.work(); // Output: Hoàng đang lập trình bằng ngôn ngữ Java
        
        System.out.println("Lương Manager: " + manager.calculateSalary()); 
        // Output: Lương Manager: 4500.0 (2000 + 5 * 500)
    }
}

Tại sao cách làm này lại hiệu quả?

  • Kế thừa tài nguyên: Cả DeveloperManager đều sử dụng lại thuộc tính namebaseSalary từ Employee mà không cần khai báo lại.
  • Tùy biến linh hoạt: Mỗi lớp con ghi đè (@Override) các phương thức để thực hiện đúng logic nghiệp vụ riêng (Lập trình viên thì “code”, Quản lý thì có “thưởng thêm”).
  • Dễ dàng mở rộng: Nếu sau này bạn cần thêm lớp Designer, bạn chỉ việc extends Employee mà không làm ảnh hưởng đến code hiện tại.

Các nguyên tắc vàng của extends trong Java: Đơn kế thừa & quan hệ IS-A

Quan hệ “IS-A” (Là một)

Một quy tắc vàng khi dùng extends là kiểm tra quan hệ IS-A. Bạn chỉ nên sử dụng kế thừa khi lớp con thực sự là một phiên bản cụ thể của lớp cha.

  • Đúng: Dog IS-A Animal (Con chó là một loài động vật).
  • Sai: Engine IS-A Car (Động cơ không phải là một chiếc xe hơi, đây là quan hệ “HAS-A” – Động cơ thuộc về xe hơi).

Đơn kế thừa

Một đặc điểm quan trọng cần lưu ý: Java chỉ hỗ trợ đơn kế thừa. Điều này có nghĩa là một lớp con chỉ có thể extends từ duy nhất một lớp cha.

Tại sao Java không cho phép đa kế thừa lớp? Java cấm đa kế thừa để tránh lỗi kim cương (Diamond Problem). Hãy tưởng tượng nếu lớp C kế thừa từ cả A và B, và cả A, B đều có phương thức run(). Khi đó, lớp C sẽ không biết phải thực thi phương thức run() của lớp nào, gây ra sự xung đột và nhập nhằng trong hệ thống. 

Nếu bạn cần một lớp có đặc điểm của nhiều nguồn khác nhau, Java khuyến khích sử dụng Interface thay vì kế thừa lớp. Một lớp có thể triển khai (implements) không giới hạn số lượng Interface.

Từ khóa extends vs implements có gì khác nhau?

Rất nhiều người mới bắt đầu thường nhầm lẫn giữa việc khi nào dùng extends và khi nào dùng implements. Bảng dưới đây sẽ giúp bạn phân loại nhanh chóng:

Đặc điểm Extends Implememts
Đối tượng áp dụngGiữa 2 Class hoặc giữa 2 Interface.Giữa một Class và một hoặc nhiều Interface.
Mục đíchTạo mối quan hệ “IS-A”, kế thừa các đặc tính và hành vi có sẵn.Thực thi các cam kết về hành vi (bản thiết kế) mà Interface đặt ra.
Số lượngChỉ duy nhất một (Đơn kế thừa lớp).Có thể triển khai nhiều Interface cùng lúc.
Bản chấtLớp con lấy lại những gì lớp cha đã có.Lớp thực hiện phải tự định nghĩa lại các phương thức trừu tượng của Interface.

Các trường hợp sử dụng cụ thể:

  • Class extends Class: 
class Dog extends Animal

→ Class Dog kế thừa Class Animal

  • Interface extends Interface: 
interface Readable extends Printable

→ Cho phép Interface mở rộng đặc tính của các Interface khác.

  • Class implements Interface: 
class Dog implements Pet

→ Class cam kết thực thi các phương thức trừu tượng (hành vi) mà Interface định ra.

Các câu hỏi thường gặp về extends trong Java

Một lớp có thể extends nhiều lớp cùng lúc không?

Không. Java không hỗ trợ đa kế thừa lớp. Một lớp chỉ có thể kế thừa từ duy nhất một lớp cha để tránh lỗi Diamond (nhập nhằng trong việc gọi phương thức). Tuy nhiên, một lớp có thể triển khai (implements) nhiều Interface cùng lúc.

Có lớp nào trong Java không thể bị kế thừa không?

Có. Nếu một lớp được khai báo với từ khóa final, không một lớp nào khác có thể extends nó. Ví dụ: Lớp String trong Java là một final class, bạn không thể tạo ra một lớp con của String.

Lớp con có kế thừa được Constructor của lớp cha không?

Không, Constructor không phải là thành viên của lớp nên không được kế thừa. Tuy nhiên, lớp con có thể gọi Constructor của lớp cha thông qua từ khóa super().

Khi nào nên dùng extends và khi nào nên dùng implements?

Dùng extends khi có quan hệ “Là một” (IS-A). Ví dụ: “Mèo là một loài động vật”.

Dùng implements khi muốn thực hiện một “Khả năng” hoặc “Vai trò” (CAN-DO). Ví dụ: “Lớp Xe và lớp Người đều có thể triển khai Interface Moveable (Có thể di chuyển)”.

Lớp con có thể truy cập các thuộc tính private của lớp cha không?

Không trực tiếp. Các thành phần private chỉ có ý nghĩa trong nội bộ lớp đó. Để lớp con có thể truy cập, bạn nên chuyển chúng thành protected hoặc sử dụng các hàm getter/setter public ở lớp cha.

Kết luận 

Từ khóa extends là công cụ quan trọng để hiện thực hóa tính kế thừa trong Java, giúp tối ưu hóa việc tái sử dụng mã nguồn và xây dựng hệ thống phân cấp logic. Tuy nhiên, để sử dụng hiệu quả, bạn cần nhớ kỹ nguyên tắc đơn kế thừa và chỉ áp dụng khi thực sự có mối quan hệ “IS-A”.

Hy vọng bài viết này đã giúp bạn nắm vững cách vận hành của extends. Hãy kết hợp linh hoạt giữa kế thừa lớp và Interface để tạo nên những chương trình Java mạnh mẽ và dễ bảo trì hơn!

TÁC GIẢ
Tien Tran
Tien Tran

iOS Developer

Có 4 năm kinh nghiệm trong lĩnh vực phát triển ứng dụng mobile, được chứng minh qua lịch sử làm việc ở các công ty lớn (VCCorp, KiotViet, Vega Fintech). Với đam mê tìm hiểu, nghiên cứu những kiến thức chuyên môn cần có của một lập trình viên mobile hiện nay như Swift, Objective C, Flutter, Kotlin,... Tiến mong muốn chia sẻ kinh nghiệm làm việc và truyền cảm hứng cho mọi người muốn theo đuổi con đường trở thành một nhà phát triển ứng dụng di động chuyên nghiệp trong tương lai.