1. Giới thiệu
Java là một ngôn ngữ lập trình được sử dụng rộng rãi trong nhiều lĩnh vực, từ hệ thống doanh nghiệp đến các ứng dụng web và phát triển Android. Trong số rất nhiều tính năng của nó, “kế thừa” là một trong những khái niệm quan trọng nhất khi học lập trình hướng đối tượng.
Bằng cách sử dụng kế thừa, một lớp mới (lớp con/lớp con cấp dưới) có thể tiếp nhận chức năng của một lớp hiện có (lớp cha/lớp cha cấp trên). Điều này giúp giảm việc lặp lại mã và làm cho các chương trình dễ mở rộng và bảo trì hơn. Trong Java, kế thừa được thực hiện bằng từ khóa extends.
Trong bài viết này, chúng tôi sẽ giải thích rõ vai trò của từ khóa extends trong Java, cách sử dụng cơ bản, các ứng dụng thực tế và các câu hỏi thường gặp. Hướng dẫn này hữu ích không chỉ cho người mới bắt đầu với Java mà còn cho những ai muốn ôn lại kiến thức về kế thừa. Khi đọc xong, bạn sẽ hiểu đầy đủ về ưu và nhược điểm của kế thừa cũng như các cân nhắc thiết kế quan trọng.
Hãy bắt đầu bằng cách xem xét kỹ hơn “Kế thừa trong Java là gì?”.
2. Kế thừa trong Java là gì?
Kế thừa trong Java là một cơ chế mà trong đó một lớp (lớp cha/lớp cha cấp trên) truyền các đặc điểm và chức năng của mình cho một lớp khác (lớp con/lớp con cấp dưới). Nhờ có kế thừa, các trường (biến) và phương thức (hàm) được định nghĩa trong lớp cha có thể được tái sử dụng trong lớp con.
Cơ chế này giúp việc tổ chức và quản lý mã trở nên dễ dàng hơn, tập trung các quy trình chung, và mở rộng hoặc thay đổi chức năng một cách linh hoạt. Kế thừa là một trong ba trụ cột cốt lõi của Lập trình Hướng Đối tượng (OOP), cùng với đóng gói và đa hình.
Về mối quan hệ “là một”
Một ví dụ phổ biến về kế thừa là mối quan hệ “là một”. Ví dụ, “một Con chó là một Động vật”. Điều này có nghĩa là lớp Dog kế thừa từ lớp Animal. Con chó có thể nhận các đặc điểm và hành vi của một Động vật đồng thời thêm vào những tính năng riêng của mình.
class Animal {
void eat() {
System.out.println("食べる");
}
}
class Dog extends Animal {
void bark() {
System.out.println("ワンワン");
}
}
Trong ví dụ này, lớp Dog kế thừa từ lớp Animal. Một đối tượng Dog có thể sử dụng cả phương thức bark và phương thức eat được kế thừa.
Điều gì xảy ra khi bạn sử dụng kế thừa?
- Bạn có thể tập trung logic và dữ liệu chung vào lớp cha, giảm nhu cầu viết lại cùng một đoạn mã trong mỗi lớp con.
- Mỗi lớp con có thể thêm các hành vi độc đáo của riêng mình hoặc ghi đè (override) các phương thức của lớp cha.
Việc sử dụng kế thừa giúp tổ chức cấu trúc chương trình và làm cho việc bổ sung tính năng cũng như bảo trì trở nên dễ dàng hơn. Tuy nhiên, kế thừa không phải lúc nào cũng là lựa chọn tốt nhất, và quan trọng là phải đánh giá cẩn thận xem có thực sự tồn tại một mối quan hệ “là một” hợp lý trong thiết kế hay không.
3. Cách hoạt động của từ khóa extends
Từ khóa extends trong Java khai báo một cách rõ ràng việc kế thừa lớp. Khi một lớp con kế thừa chức năng của lớp cha, cú pháp extends ParentClassName được sử dụng trong phần khai báo lớp. Điều này cho phép lớp con sử dụng trực tiếp tất cả các thành viên công khai (trường và phương thức) của lớp cha.
Cú pháp cơ bản
class ParentClass {
// Fields and methods of the parent class
}
class ChildClass extends ParentClass {
// Fields and methods unique to the child class
}
Ví dụ, sử dụng các lớp Animal và Dog đã đề cập ở trên, chúng ta có:
class Animal {
void eat() {
System.out.println("食べる");
}
}
class Dog extends Animal {
void bark() {
System.out.println("ワンワン");
}
}
Bằng cách viết Dog extends Animal, lớp Dog kế thừa từ lớp Animal và có thể sử dụng phương thức eat.
Sử dụng thành viên của lớp cha
Với kế thừa, một đối tượng của lớp con có thể truy cập các phương thức và trường của lớp cha (miễn là các bộ điều chỉnh truy cập cho phép):
Dog dog = new Dog();
dog.eat(); // Calls the parent class method
dog.bark(); // Calls the child class method
Lưu ý quan trọng
- Java chỉ cho phép kế thừa từ một lớp (kế thừa đơn). Bạn không thể chỉ định nhiều lớp sau
extends. - Nếu bạn muốn ngăn chặn kế thừa, bạn có thể sử dụng modifier
finaltrên lớp.
Mẹo Phát Triển Thực Tế
Sử dụng extends một cách đúng đắn cho phép bạn tập trung chức năng chung vào lớp cha và mở rộng hoặc tùy chỉnh hành vi trong các lớp con. Nó cũng hữu ích khi bạn muốn thêm tính năng mới mà không sửa đổi mã hiện có.
4. Ghi đè Phương thức và Từ khóa super
Khi sử dụng kế thừa, có những trường hợp bạn muốn thay đổi hành vi của một phương thức được định nghĩa trong lớp cha. Điều này được gọi là “ghi đè phương thức.” Trong Java, ghi đè được thực hiện bằng cách định nghĩa một phương thức trong lớp con có cùng tên và cùng danh sách tham số như phương thức trong lớp cha.
Ghi đè Phương thức
Khi ghi đè một phương thức, thường thêm annotation @Override. Điều này giúp trình biên dịch phát hiện lỗi ngẫu nhiên như tên phương thức sai hoặc chữ ký.
class Animal {
void eat() {
System.out.println("食べる");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("ドッグフードを食べる");
}
}
Trong ví dụ này, lớp Dog ghi đè phương thức eat. Khi gọi eat trên một instance của Dog, đầu ra sẽ là “ドッグフードを食べる”.
Dog dog = new Dog();
dog.eat(); // Displays: ドッグフードを食べる
Sử dụng Từ khóa super
Nếu bạn muốn gọi phương thức gốc của lớp cha bên trong phương thức bị ghi đè, sử dụng từ khóa super.
class Dog extends Animal {
@Override
void eat() {
super.eat(); // Calls the parent class’s eat()
System.out.println("ドッグフードも食べる");
}
}
Điều này thực thi phương thức eat của lớp cha trước và sau đó thêm hành vi của lớp con.
Các Constructor và super
Nếu lớp cha có constructor với tham số, lớp con phải gọi nó một cách rõ ràng bằng super(arguments) làm dòng đầu tiên của constructor của nó.
class Animal {
Animal(String name) {
System.out.println("Animal: " + name);
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
System.out.println("Dog: " + name);
}
}
Tóm tắt
- Ghi đè nghĩa là định nghĩa lại phương thức của lớp cha trong lớp con.
- Sử dụng annotation
@Overrideđược khuyến nghị. - Sử dụng
superkhi bạn muốn tái sử dụng triển khai phương thức của lớp cha. supercũng được sử dụng khi gọi constructor của lớp cha.
5. Ưu điểm và Nhược điểm của Kế thừa
Sử dụng kế thừa trong Java mang lại nhiều lợi ích cho thiết kế và phát triển chương trình. Tuy nhiên, sử dụng sai có thể dẫn đến các vấn đề nghiêm trọng. Dưới đây, chúng tôi giải thích chi tiết về ưu điểm và nhược điểm.
Ưu điểm của Kế thừa
- Cải thiện khả năng tái sử dụng mã Định nghĩa logic và dữ liệu chung trong lớp cha loại bỏ nhu cầu lặp lại mã giống nhau trong mỗi lớp con. Điều này giảm trùng lặp và cải thiện khả năng bảo trì và đọc hiểu.
- Dễ dàng mở rộng Khi cần chức năng mới, bạn có thể tạo lớp con mới dựa trên lớp cha mà không sửa đổi mã hiện có. Điều này giảm thiểu tác động thay đổi và giảm cơ hội lỗi.
- Cho phép đa hình Kế thừa cho phép “một biến của lớp cha tham chiếu đến instance của lớp con.” Điều này cho phép thiết kế linh hoạt sử dụng giao diện chung và hành vi đa hình.
Nhược điểm của Kế thừa
Cây kế thừa sâu làm cho thiết kế phức tạp
Nếu chuỗi kế thừa trở nên quá sâu, sẽ khó hiểu hành vi được định nghĩa ở đâu, khiến việc bảo trì trở nên khó khăn hơn.Thay đổi trong lớp cha ảnh hưởng tới tất cả các lớp con
Việc sửa đổi hành vi của lớp cha có thể vô tình gây ra vấn đề cho tất cả các lớp con. Các lớp cha đòi hỏi thiết kế cẩn thận và cập nhật thường xuyên.Có thể giảm tính linh hoạt của thiết kế
Việc lạm dụng kế thừa làm các lớp bị ràng buộc chặt chẽ, khiến việc thay đổi trong tương lai trở nên khó khăn. Trong một số trường hợp, quan hệ “có‑một” (has-a) sử dụng composition linh hoạt hơn so với kế thừa “là‑một” (is-a).
Tóm tắt
Kế thừa là một công cụ mạnh mẽ, nhưng dựa vào nó cho mọi thứ có thể gây ra các vấn đề lâu dài. Luôn kiểm tra xem có thực sự tồn tại quan hệ “là‑một” hay không và chỉ áp dụng kế thừa khi phù hợp.
6. Sự khác nhau giữa Kế thừa và Giao diện
Java cung cấp hai cơ chế quan trọng để mở rộng và tổ chức chức năng: kế thừa lớp (extends) và giao diện (implements). Cả hai đều hỗ trợ tái sử dụng mã và thiết kế linh hoạt, nhưng cấu trúc và mục đích sử dụng của chúng khác nhau đáng kể. Dưới đây, chúng tôi giải thích các khác biệt và cách lựa chọn giữa chúng.
Sự khác nhau giữa extends và implements
- extends (Kế thừa)
- Bạn chỉ có thể kế thừa từ một lớp duy nhất (kế thừa đơn).
- Các trường và phương thức đã được triển khai đầy đủ từ lớp cha có thể được sử dụng trực tiếp trong lớp con.
Biểu thị một quan hệ “là‑một” (ví dụ: Dog là một Animal).
implements (Triển khai Giao diện)
- Nhiều giao diện có thể được triển khai đồng thời.
- Giao diện chỉ chứa các khai báo phương thức (mặc dù các phương thức mặc định tồn tại từ Java 8 trở đi).
- Biểu thị một quan hệ “có‑thể‑làm” (ví dụ: Dog có thể sủa, Dog có thể đi).
Ví dụ về việc sử dụng Giao diện
interface Walkable {
void walk();
}
interface Barkable {
void bark();
}
class Dog implements Walkable, Barkable {
public void walk() {
System.out.println("歩く");
}
public void bark() {
System.out.println("ワンワン");
}
}
Trong ví dụ này, lớp Dog triển khai hai giao diện, Walkable và Barkable, cung cấp hành vi tương tự như kế thừa đa dạng. 
Tại sao Giao diện lại cần thiết
Java cấm kế thừa đa lớp vì nó có thể gây ra xung đột khi các lớp cha định nghĩa cùng một phương thức hoặc trường. Giao diện giải quyết vấn đề này bằng cách cho phép một lớp nhận nhiều “kiểu” mà không kế thừa các triển khai mâu thuẫn.
Cách sử dụng chúng một cách đúng đắn
- Sử dụng
extendskhi tồn tại một quan hệ “là‑một” rõ ràng giữa các lớp. - Sử dụng
implementskhi bạn muốn cung cấp các hợp đồng hành vi chung cho nhiều lớp.
Ví dụ:
- “A Dog is an Animal” →
Dog extends Animal - “A Dog can walk and can bark” →
Dog implements Walkable, Barkable
Tóm tắt
- Một lớp chỉ có thể kế thừa từ một lớp cha duy nhất, nhưng có thể triển khai nhiều giao diện.
- Lựa chọn giữa kế thừa và giao diện dựa trên mục đích thiết kế sẽ mang lại mã sạch, linh hoạt và dễ bảo trì.
7. Các thực hành tốt nhất khi sử dụng Kế thừa
Kế thừa trong Java là một công cụ mạnh mẽ, nhưng việc sử dụng không đúng có thể làm cho chương trình cứng nhắc và khó bảo trì. Dưới đây là các thực hành tốt nhất và hướng dẫn để sử dụng kế thừa một cách an toàn và hiệu quả.
Khi nào nên sử dụng Kế thừa — và khi nào nên tránh
- Sử dụng kế thừa khi:
- Có một quan hệ “là‑một” rõ ràng (ví dụ: Dog là một Animal).
- Bạn muốn tái sử dụng chức năng của lớp cha và mở rộng nó.
Bạn muốn loại bỏ mã trùng lặp và tập trung logic chung.
Tránh kế thừa khi:
- Bạn chỉ dùng nó để tái sử dụng mã (điều này thường dẫn đến thiết kế lớp không tự nhiên).
- Một quan hệ “có‑một” phù hợp hơn — trong những trường hợp này, hãy cân nhắc composition.
Lựa chọn giữa Kế thừa và Composition
- Kế thừa (
extends): quan hệ là một - Ví dụ:
Dog extends Animal - Có ích khi lớp con thực sự đại diện cho một loại của lớp cha.
- Tổ hợp (quan hệ có một)
- Ví dụ: Một chiếc xe có một Engine
- Sử dụng một thể hiện của lớp khác bên trong để thêm chức năng.
- Linh hoạt hơn và dễ dàng thích nghi với các thay đổi trong tương lai.
Hướng dẫn thiết kế để ngăn ngừa việc lạm dụng kế thừa
- Không tạo ra các cây kế thừa quá sâu (giữ tối đa 3 cấp hoặc ít hơn).
- Nếu có nhiều lớp con kế thừa từ cùng một lớp cha, hãy đánh giá lại xem trách nhiệm của lớp cha có phù hợp không.
- Luôn cân nhắc rủi ro khi thay đổi lớp cha sẽ ảnh hưởng tới tất cả các lớp con.
- Trước khi áp dụng kế thừa, hãy xem xét các lựa chọn thay thế như giao diện và tổ hợp.
Hạn chế kế thừa bằng từ khóa final
- Thêm
finalvào một lớp sẽ ngăn không cho nó được kế thừa. - Thêm
finalvào một phương thức sẽ ngăn không cho nó bị ghi đè bởi các lớp con.final class Utility { // This class cannot be inherited } class Base { final void show() { System.out.println("オーバーライド禁止"); } }
Cải thiện tài liệu và chú thích
- Việc ghi lại các quan hệ kế thừa và ý định thiết kế lớp trong Javadoc hoặc chú thích giúp việc bảo trì trong tương lai trở nên dễ dàng hơn rất nhiều.
Tóm tắt
Kế thừa rất tiện lợi, nhưng phải được sử dụng một cách có chủ đích. Luôn tự hỏi, “Liệu lớp này thực sự là một loại của lớp cha không?” Nếu không chắc, hãy xem xét tổ hợp hoặc giao diện như các lựa chọn thay thế.
8. Tóm tắt
Cho đến thời điểm này, chúng ta đã giải thích chi tiết về kế thừa trong Java và từ khóa extends — từ những nguyên tắc cơ bản đến cách sử dụng thực tế. Dưới đây là tóm tắt các điểm chính đã được đề cập trong bài viết.
- Kế thừa trong Java cho phép một lớp con tiếp nhận dữ liệu và chức năng của lớp cha, giúp thiết kế chương trình hiệu quả và tái sử dụng.
- Từ khóa
extendslàm rõ mối quan hệ giữa lớp cha và lớp con (“quan hệ là một”). - Ghi đè phương thức và từ khóa
supercho phép mở rộng hoặc tùy chỉnh hành vi được kế thừa. - Kế thừa mang lại nhiều lợi thế, như tái sử dụng mã, khả năng mở rộng và hỗ trợ đa hình, nhưng cũng có nhược điểm như cây kế thừa sâu hoặc phức tạp và các thay đổi có ảnh hưởng rộng.
- Hiểu sự khác biệt giữa kế thừa, giao diện và tổ hợp là điều quan trọng để chọn cách thiết kế phù hợp.
- Tránh lạm dụng kế thừa; luôn rõ ràng về mục đích và lý do thiết kế.
Kế thừa là một trong những khái niệm cốt lõi của lập trình hướng đối tượng trong Java. Bằng cách nắm vững các quy tắc và thực hành tốt, bạn sẽ có thể áp dụng nó một cách hiệu quả trong phát triển thực tế.
9. Câu hỏi thường gặp (FAQ)
Q1: Điều gì xảy ra với constructor của lớp cha khi một lớp được kế thừa trong Java?
A1: Nếu lớp cha có một constructor không có đối số (mặc định), nó sẽ được gọi tự động từ constructor của lớp con. Nếu lớp cha chỉ có constructor có tham số, lớp con phải gọi nó một cách rõ ràng bằng cách sử dụng super(arguments) ở đầu constructor của mình.
Q2: Java có thể thực hiện kế thừa đa dạng (multiple inheritance) của các lớp không?
A2: Không. Java không hỗ trợ kế thừa đa dạng của các lớp. Một lớp chỉ có thể extends một lớp cha duy nhất. Tuy nhiên, một lớp có thể implements nhiều giao diện (interface) cùng lúc.
Q3: Sự khác nhau giữa kế thừa và hợp thành (composition) là gì?
A3: Kế thừa biểu thị mối quan hệ “là một” (is-a), trong đó lớp con tái sử dụng chức năng và dữ liệu của lớp cha. Hợp thành biểu thị mối quan hệ “có một” (has-a), trong đó một lớp chứa một thể hiện của lớp khác. Hợp thành thường linh hoạt hơn và được ưu tiên trong nhiều trường hợp yêu cầu giảm sự phụ thuộc chặt chẽ hoặc mở rộng trong tương lai.
Q4: Modifier final có hạn chế việc kế thừa và ghi đè không?
A4: Có. Nếu một lớp được đánh dấu là final, nó không thể được kế thừa. Nếu một phương thức được đánh dấu là final, nó không thể bị ghi đè trong lớp con. Điều này hữu ích để đảm bảo hành vi nhất quán hoặc vì mục đích bảo mật.
Q5: Điều gì xảy ra nếu lớp cha và lớp con định nghĩa trường hoặc phương thức cùng tên?
A5: Nếu một trường cùng tên được định nghĩa trong cả hai lớp, trường trong lớp con sẽ ẩn (shadow) trường trong lớp cha. Các phương thức lại hành xử khác: nếu chữ ký (signature) trùng khớp, phương thức trong lớp con sẽ ghi đè phương thức của lớp cha. Lưu ý rằng trường không thể bị ghi đè—chỉ có thể bị ẩn.
Q6: Điều gì xảy ra nếu độ sâu kế thừa trở nên quá lớn?
A6: Các cây kế thừa sâu làm cho mã khó hiểu và bảo trì. Việc theo dõi nơi logic được định nghĩa trở nên khó khăn. Để có thiết kế dễ bảo trì, nên giữ độ sâu kế thừa nông và vai trò của các lớp được phân tách rõ ràng.
Q7: Sự khác nhau giữa ghi đè (overriding) và tải lại (overloading) là gì?
A7: Ghi đè định nghĩa lại một phương thức của lớp cha trong lớp con. Tải lại định nghĩa nhiều phương thức trong cùng một lớp có cùng tên nhưng khác nhau về kiểu hoặc số lượng tham số.
Q8: Các lớp trừu tượng (abstract class) và giao diện (interface) nên được sử dụng khác nhau như thế nào?
A8: Lớp trừu tượng được dùng khi bạn muốn cung cấp triển khai chung hoặc các trường dữ liệu cho các lớp có quan hệ. Giao diện được dùng khi bạn muốn định nghĩa các hợp đồng hành vi mà nhiều lớp có thể thực thi. Hãy dùng lớp trừu tượng cho mã chia sẻ và dùng giao diện khi muốn biểu diễn nhiều loại hoặc khi cần nhiều “khả năng” (abilities) khác nhau.
