Kế thừa (Inheritance) trong Java là cơ chế cho phép một lớp con sở hữu lại toàn bộ các thuộc tính và phương thức từ lớp cha, giúp tối ưu hóa việc tái sử dụng mã nguồn (Reusability). Việc áp dụng kế thừa cần sự tính toán kỹ lưỡng để tránh tạo ra cấu trúc phân cấp quá phức tạp và khó bảo trì.
Đọc bài viết để hiểu thêm về:
- Cú pháp và cách hoạt động của kế thừa trong Java
- Các loại kế thừa trong Java
- Tại sao Java không hỗ trợ đa kế thừa với Class?
- Từ khóa super và ghi đè phương thức (Method Overriding)
- Những thành phần không được kế thừa
- Khi nào nên dùng kế thừa?
- Các câu hỏi thường gặp về kế thừa trong Java
Cách hoạt động của kế thừa trong Java
Ý tưởng của kế thừa rất thực tế: Khi bạn cần tạo một lớp mới nhưng đã có sẵn một lớp khác chứa một phần mã nguồn bạn mong muốn, bạn có thể cho lớp mới kế thừa lại lớp đó. Việc này cho phép bạn sử dụng lại các trường dữ liệu và phương thức đã có mà không cần tốn công viết lại hay kiểm thử từ đầu.
Cơ chế này được xây dựng dựa trên mối quan hệ “IS-A” (là một), ví dụ như “Xe hơi là một Phương tiện” hay “Lập trình viên là một Nhân viên”.
Về mặt kỹ thuật, một lớp con sẽ thừa hưởng tất cả các thành viên (biến, phương thức và lớp lồng nhau) từ lớp cha của nó. Trong lập trình Java, các thành phần này thường được gọi bằng các cặp tên sau:
- Lớp cha: Parent Class, Super Class hoặc Base Class.
- Lớp con: Child Class, Sub Class hoặc Derived Class.
Tuy nhiên, bạn cần lưu ý rằng Constructor (hàm khởi tạo) không được coi là thành viên, vì vậy chúng không được kế thừa trực tiếp. Thay vào đó, lớp con sẽ gọi đến Constructor của lớp cha khi cần thiết.
Đọc thêm: Java là gì? Tất cả những điều bạn cần biết về ngôn ngữ Java
Cú pháp của kế thừa trong Java với từ khóa extends
Để thiết lập mối quan hệ này, Java sử dụng từ khóa extends. Bạn có thể tham khảo chi tiết hơn về các quy tắc và giới hạn tại bài viếtTừ khóa extends trong Java.
Ví dụ minh họa:
// Lớp cha (Super Class)
class Animal {
void eat() {
System.out.println("Đang ăn...");
}
}
// Lớp con (Sub Class) thừa hưởng từ Animal
class Dog extends Animal {
void bark() {
System.out.println("Gâu gâu!");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
// Sử dụng phương thức thừa hưởng từ lớp Animal
myDog.eat();
// Sử dụng phương thức riêng của lớp Dog
myDog.bark();
}
}
Ở ví dụ này, dù lớp Dog không khai báo phương thức eat(), đối tượng myDog vẫn thực hiện được hành động này nhờ vào cơ chế kế thừa từ Animal.
Các loại kế thừa trong Java
Java hỗ trợ ba kiểu kế thừa chính dựa trên giai cấp của lớp (Class). Việc phân loại này giúp bạn tổ chức cấu trúc đối tượng một cách logic và khoa học:
- Đơn kế thừa (Single Inheritance): Một lớp con chỉ kế thừa trực tiếp từ duy nhất một lớp cha. Đây là kiểu phổ biến nhất, đảm bảo tính đơn giản và rõ ràng của mã nguồn.
- Kế thừa đa cấp (Multilevel Inheritance): Tạo ra một chuỗi kế thừa theo dạng “ông – cha – con”. Ví dụ: Lớp C kế thừa lớp B, lớp B lại kế thừa từ lớp A. Lúc này, lớp C sẽ sở hữu đặc tính của cả A và B.
- Kế thừa thứ bậc (Hierarchical Inheritance): Nhiều lớp con cùng kế thừa chung từ một lớp cha. Ví dụ: Cả lớp
DogvàCatđều cùng kế thừa từ lớpAnimal.

Sơ đồ cấu trúc phân cấp các lớp (Class Hierarchy) kế thừa từ lớp Object.
Tại sao Java không hỗ trợ đa kế thừa với Class?
Nhiều ngôn ngữ như C++ cho phép một lớp kế thừa từ nhiều lớp cha cùng lúc. Tuy nhiên, Java đã loại bỏ khả năng này để tránh vấn đề “Kim cương” (Diamond Problem).
Hãy tưởng tượng nếu lớp C cùng kế thừa từ lớp A và lớp B. Nếu cả A và B đều có một phương thức giống hệt tên nhau nhưng cách xử lý khác nhau, Java sẽ không thể xác định lớp C nên sử dụng phương thức của bên nào. Điều này gây ra sự nhập nhằng và tiềm ẩn lỗi nghiêm trọng trong hệ thống.
Giải pháp: Sử dụng Interface
Để giải quyết nhu cầu một đối tượng cần mang đặc tính của nhiều nguồn khác nhau, Java cung cấp Interface. Một lớp chỉ có thể kế thừa (extends) một lớp cha, nhưng có thể triển khai (implements) không giới hạn số lượng Interface.
So sánh Inheritance (Class) và Interface
Dưới đây là bảng so sánh nhanh giúp bạn phân biệt khi nào nên dùng kế thừa lớp và khi nào nên dùng Interface:
| Đặc điểm | Kế thừa (Inheritance – Class) | Interface |
| Bản chất | Mối quan hệ “IS-A” (Là một). | Mối quan hệ “CAN-DO” (Có khả năng). |
| Số lượng | Chỉ được kế thừa duy nhất 1 lớp cha. | Có thể thực thi nhiều Interface cùng lúc. |
| Mục đích | Tái sử dụng mã nguồn và chia sẻ thuộc tính. | Thiết lập bộ quy tắc (hành vi) chung. |
| Phương thức | Có thể chứa cả phương thức trừu tượng và phương thức có thân hàm. | Chủ yếu chứa các phương thức trừu tượng (không có thân hàm). |
Ví dụ Code minh hoạ:
// 1. Interface định nghĩa "Khả năng" (CAN-DO)
interface Flyable {
void fly(); // Quy tắc: Ai thực thi interface này đều phải biết bay
}
// 2. Lớp cha định nghĩa "Bản chất" (IS-A)
abstract class Bird {
String name;
Bird(String name) {
this.name = name;
}
void eat() {
System.out.println(name + " đang ăn...");
}
abstract void makeSound(); // Mỗi loài chim kêu một kiểu khác nhau
}
// 3. Lớp Eagle: Kế thừa 1 lớp và thực thi 1 Interface
class Eagle extends Bird implements Flyable {
Eagle(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println("Tiếng đại bàng: Éc éc!");
}
@Override
public void fly() {
System.out.println(name + " đang bay lượn trên bầu trời cao.");
}
}
// 4. Lớp Penguin: Chỉ kế thừa lớp Bird (vì cánh cụt không biết bay)
class Penguin extends Bird {
Penguin(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println("Tiếng chim cánh cụt: Cạp cạp!");
}
}
// --- Hàm Main để chạy thử ---
public class Main {
public static void main(String[] args) {
Eagle myEagle = new Eagle("Đại bàng vàng");
Penguin myPenguin = new Penguin("Cánh cụt Pingu");
// Cả hai đều có hành vi từ lớp cha Bird
myEagle.eat();
myPenguin.eat();
// Chỉ Đại bàng mới có khả năng bay (Interface)
myEagle.fly();
// myPenguin.fly(); // Lỗi biên dịch vì Penguin không implement Flyable
}
}
Việc kết hợp linh hoạt giữa kế thừa lớp và Interface chính là chìa khóa để tạo ra những hệ thống Java vừa chặt chẽ, vừa linh hoạt.
Từ khóa super và Ghi đè phương thức (Method Overriding)
Khi làm việc với kế thừa, không phải lúc nào lớp con cũng sử dụng nguyên bản những gì lớp cha cung cấp. Đôi khi, bạn cần tham chiếu ngược lại lớp cha hoặc thay đổi hoàn toàn hành vi của một phương thức.
Từ khóa super
super là một biến tham chiếu được sử dụng để trỏ trực tiếp đến đối tượng của lớp cha gần nhất. Bạn sẽ cần đến nó trong hai trường hợp phổ biến:
- Gọi Constructor của lớp cha: Như đã đề cập, Constructor không được kế thừa. Vì vậy, ta dùng
super()để kích hoạt hàm khởi tạo của lớp cha ngay bên trong hàm khởi tạo của lớp con. - Truy cập thành phần bị trùng tên: Khi lớp con định nghĩa một thuộc tính hoặc phương thức trùng tên với lớp cha (ẩn danh), bạn có thể dùng
super.tenPhuongThuc()để chỉ định rõ ràng rằng bạn muốn gọi phiên bản của lớp cha.
Ghi đè phương thức (Method Overriding)
Ghi đè phương thức xảy ra khi lớp con định nghĩa lại một phương thức đã tồn tại ở lớp cha để phù hợp với đặc thù riêng của nó. Đây chính là cách Java thực hiện tính đa hình tại thời điểm thực thi (Runtime Polymorphism).
Quy tắc và Annotation @Override
Để ghi đè thành công, phương thức ở lớp con phải có cùng tên, cùng tham số và cùng kiểu trả về với phương thức ở lớp cha.
Sử dụng Annotation @Override trước phương thức ở lớp con là một quy chuẩn quan trọng:
- Giúp trình biên dịch kiểm tra lỗi: Nếu bạn viết sai tên phương thức hoặc tham số, Java sẽ báo lỗi ngay lập tức.
- Tăng tính dễ đọc: Giúp cộng sự hiểu rõ đây là hành vi được tùy chỉnh lại từ lớp cha.
Ví dụ minh họa:
class Animal {
void makeSound() {
System.out.println("Động vật phát ra tiếng kêu");
}
}
class Cat extends Animal {
@Override
void makeSound() {
// Gọi phương thức của lớp cha nếu cần
super.makeSound();
// Sau đó thực hiện hành vi riêng
System.out.println("Meo meo!");
}
}
Trong ví dụ này, lớp Cat đã tùy biến lại phương thức makeSound(). Khi bạn gọi phương thức này từ một đối tượng Cat, chương trình sẽ ưu tiên thực hiện logic “Meo meo” thay vì logic chung chung của lớp Animal.

Những thành phần không được kế thừa trong Java
Mặc dù kế thừa cho phép lớp con sở hữu các đặc tính của lớp cha, nhưng không phải mọi thứ đều được “chuyển giao”. Java đặt ra những giới hạn nghiêm ngặt để đảm bảo tính đóng gói và an toàn hệ thống:
- Private: Các thuộc tính và phương thức được đánh dấu là
privatethì không thể được truy cập trực tiếp từ lớp con. Chúng được coi là tài sản riêng của lớp cha. Tuy nhiên, lớp con vẫn có thể tương tác với các dữ liệu này gián tiếp thông qua các phương thứcpublichoặcprotected(như Getter/Setter) của lớp cha. - Hàm khởi tạo (Constructors): Như đã đề cập, lớp con không kế thừa Constructor của lớp cha. Mỗi lớp có nhiệm vụ tự khởi tạo trạng thái cho chính mình. Tuy nhiên, lớp con bắt buộc phải gọi Constructor của lớp cha (thông qua
super()) để đảm bảo các thành phần chung được thiết lập đúng cách trước khi thực hiện logic riêng. - Lớp Final (Final class): Khi một lớp được khai báo với từ khóa
final, nó được coi là một “điểm dừng” trong hệ thống phân cấp. Các lớp khác không thể kế thừa từ một lớpfinal. Đây là cách Java bảo vệ các lớp quan trọng (như lớpString) không bị thay đổi hành vi gốc.
Khi nào nên dùng kế thừa?
Kế thừa rất mạnh mẽ nhưng nếu dùng sai cách, nó sẽ biến hệ thống của bạn thành một mớ hỗn độn khó bảo trì. Dưới đây là những nguyên tắc vàng để quyết định có nên sử dụng kế thừa hay không:
Chỉ dùng khi quan hệ thực sự là “IS-A”
Trước khi viết từ khóa extends, hãy tự đặt câu hỏi: “Lớp con có thực sự là một phiên bản đặc biệt của lớp cha không?”.
- Đúng:
SamsungIS-ASmartphone. - Sai:
EngineIS-ACar(Động cơ không phải là một chiếc xe hơi, nó chỉ là một bộ phận của xe).
Tránh lạm dụng kế thừa quá sâu (Deep Hierarchies)
Việc tạo ra một cây kế thừa quá nhiều tầng (ví dụ: Lớp A -> B -> C -> D -> E) sẽ gây ra tình trạng “giòn” mã nguồn. Một thay đổi nhỏ ở lớp A có thể làm hỏng logic của tất cả các lớp con phía dưới. Quy tắc ngầm trong thiết kế hệ thống là giữ cho cấu trúc phân cấp càng nông càng tốt để dễ kiểm soát và gỡ lỗi.
Phân biệt Kế thừa (Inheritance) và Tổng hợp (Composition)
Trong lập trình, có một câu châm ngôn nổi tiếng: “Ưu tiên tổng hợp hơn kế thừa” (Favor composition over inheritance).
- Kế thừa (Inheritance): Thiết lập mối quan hệ “IS-A” (Là một). Dùng khi bạn muốn chia sẻ hành vi chung và tận dụng tính đa hình.
- Tổng hợp (Composition): Thiết lập mối quan hệ “HAS-A” (Có một). Thay vì kế thừa, bạn khai báo một đối tượng của lớp khác làm thuộc tính bên trong lớp của mình.
Khi nào nên chọn Inheritance hay Composition?
| Tình huống | Lựa chọn tối ưu |
| Khi đối tượng A là một loại cụ thể của đối tượng B. | Inheritance |
| Khi đối tượng A được cấu tạo từ đối tượng B. | Composition |
| Khi bạn muốn thay đổi hành vi của đối tượng linh hoạt ngay lúc chương trình đang chạy. | Composition |
| Khi bạn muốn lớp con tự động có tất cả các tính năng của lớp cha mà không cần viết lại. | Inheritance |
Ví dụ: Một chiếc Car không nên kế thừa từ Engine. Thay vào đó, Car nên có một biến thuộc tính là Engine (Composition). Điều này giúp bạn dễ dàng thay đổi các loại động cơ khác nhau cho xe mà không cần thay đổi cấu trúc lớp.
Các câu hỏi thường gặp về kế thừa trong Java
Một lớp có thể kế thừa từ nhiều lớp cha cùng lúc không?
Không. Trong Java, một lớp chỉ có thể kế thừa từ duy nhất một lớp cha (Single Inheritance). Điều này giúp tránh xung đột và nhầm lẫn khi các lớp cha có các phương thức trùng tên nhau (Vấn đề “Kim cương”). Để đạt được hiệu quả tương tự đa kế thừa, bạn nên sử dụng Interface.
Tại sao tôi nên dùng @Override khi ghi đè phương thức?
Mặc dù không bắt buộc về mặt cú pháp, nhưng @Override giúp trình biên dịch kiểm tra xem bạn có thực sự đang ghi đè một phương thức từ lớp cha hay không. Nếu bạn viết sai tên phương thức (ví dụ: makeSounds() thay vì makeSound()), trình biên dịch sẽ báo lỗi ngay lập tức thay vì tạo ra một phương thức mới hoàn toàn.
Sự khác biệt giữa this và super là gì?
- this: Tham chiếu đến đối tượng hiện tại của lớp đó.
- super: Tham chiếu đến đối tượng của lớp cha gần nhất. Cả hai đều thường được dùng để phân biệt khi tên thuộc tính hoặc phương thức ở lớp con bị trùng với lớp cha hoặc tham số truyền vào.
Có cách nào ngăn một phương thức không bị ghi đè ở lớp con không?
Có. Bạn hãy thêm từ khóa final vào trước phương thức ở lớp cha. Ví dụ: public final void show(). Khi đó, các lớp con kế thừa từ lớp này sẽ không thể định nghĩa lại phương thức show() được nữa.
Private member có thực sự được kế thừa không?
Về mặt lý thuyết, lớp con vẫn chứa các thành phần private của lớp cha nhưng nó không có quyền truy cập trực tiếp. Để thao tác với các thành phần này, bạn phải thông qua các phương thức hỗ trợ (như Getter/Setter) hoặc đổi quyền truy cập sang protected.
Kết luận
Kế thừa trong Java là một trong bốn trụ cột quan trọng nhất của lập trình hướng đối tượng trong Java. Hy vọng qua bài viết này, bạn đã nắm vững cách sử dụng từ khóa extends, hiểu được tầm quan trọng của super và biết cách thiết kế hệ thống sao cho linh hoạt, tránh được những sai lầm phổ biến.

