
Rồi, các chiến thần code GenZ đâu rồi, lại đây anh Creyt kể chuyện nghe! Hôm nay chúng ta sẽ "bóc phốt" một từ khóa mà nhìn thì đơn giản mà sức mạnh thì "khủng long bạo chúa" trong Java: extends. Nghe tên thôi đã thấy mùi "kế thừa" rồi đúng không? Chính xác!
extends là gì mà "hot" vậy anh Creyt?
Trong thế giới lập trình hướng đối tượng (OOP) của Java, extends chính là tấm vé vàng để bạn thực hiện "kế thừa" (Inheritance). Cứ hình dung thế này: nhà mình có ông bà, bố mẹ rồi đến mình. Mình thì "kế thừa" một phần gen từ bố mẹ, bố mẹ lại "kế thừa" từ ông bà. Mình vẫn là mình, nhưng có những đặc điểm, tính cách giống bố mẹ, ông bà đúng không?
Trong code cũng vậy. Khi một class (lớp) A extends một class B, điều đó có nghĩa là class A sẽ "kế thừa" toàn bộ các thuộc tính (fields) và phương thức (methods) mà class B có (trừ những cái private mà B giấu kỹ như "mật khẩu wifi" nhà nó). Class A lúc này được gọi là lớp con (subclass/child class), còn class B là lớp cha (superclass/parent class).
Nôm na, extends giúp bạn tạo ra một mối quan hệ "là một" (is-a relationship). Ví dụ: Một Chó là một ĐộngVật. Một ÔTô là một PhươngTiện.
Để làm gì? Đơn giản thôi:
- Tái sử dụng code (Code Reusability): Thay vì viết đi viết lại những đoạn code giống nhau cho các đối tượng có chung đặc điểm, bạn chỉ cần định nghĩa chúng ở lớp cha một lần. Các lớp con cứ thế mà "xài ké", tiết kiệm thời gian và công sức như "hack game" vậy.
- Tổ chức code (Code Organization): Giúp cấu trúc chương trình rõ ràng, dễ hiểu hơn, tạo ra một hệ thống phân cấp logic. Nhìn vào là biết thằng nào "con", thằng nào "cha", thằng nào "ông nội".
- Mở rộng tính năng (Extensibility): Khi muốn thêm một loại đối tượng mới có những đặc điểm chung với đối tượng đã có, bạn chỉ cần
extendslớp cha và thêm các tính năng riêng biệt vào lớp con, không ảnh hưởng đến lớp cha.
Code Ví Dụ Minh Họa: Gia đình Động Vật
Giờ thì chiến đấu với code thôi! Anh em mình sẽ xây dựng một "hệ sinh thái" động vật đơn giản.
Đầu tiên là lớp cha DongVat:
// Lớp cha: DongVat
class DongVat {
String ten;
int tuoi;
public DongVat(String ten, int tuoi) {
this.ten = ten;
this.tuoi = tuoi;
}
public void an() {
System.out.println(ten + " đang ăn...");
}
public void ngu() {
System.out.println(ten + " đang ngủ...");
}
public void hienThiThongTin() {
System.out.println("Tên: " + ten + ", Tuổi: " + tuoi + " tuổi.");
}
}
Giờ đến lớp con Cho và Meo, chúng nó sẽ extends từ DongVat:
// Lớp con: Cho (extends DongVat)
class Cho extends DongVat {
String giong;
public Cho(String ten, int tuoi, String giong) {
// Gọi constructor của lớp cha DongVat
super(ten, tuoi);
this.giong = giong;
}
// Phương thức riêng của Cho
public void sua() {
System.out.println(ten + " đang sủa: Gâu gâu!");
}
// Ghi đè (override) phương thức an() từ lớp cha
@Override
public void an() {
System.out.println(ten + " đang ăn xương...");
}
// Ghi đè phương thức hienThiThongTin() để thêm thông tin giống
@Override
public void hienThiThongTin() {
super.hienThiThongTin(); // Gọi phương thức của lớp cha
System.out.println("Giống: " + giong);
}
}
// Lớp con: Meo (extends DongVat)
class Meo extends DongVat {
String mauLong;
public Meo(String ten, int tuoi, String mauLong) {
super(ten, tuoi);
this.mauLong = mauLong;
}
// Phương thức riêng của Meo
public void keu() {
System.out.println(ten + " đang kêu: Meo meo!");
}
// Ghi đè phương thức an() từ lớp cha
@Override
public void an() {
System.out.println(ten + " đang ăn cá...");
}
}
Và đây là cách chúng ta sử dụng chúng:
public class BaiHocExtends {
public static void main(String[] args) {
// Tạo đối tượng DongVat
DongVat dongVatChung = new DongVat("Động vật chung", 5);
dongVatChung.hienThiThongTin();
dongVatChung.an();
System.out.println("---");
// Tạo đối tượng Cho
Cho buddy = new Cho("Buddy", 3, "Golden Retriever");
buddy.hienThiThongTin(); // Kế thừa từ DongVat và thêm của Cho
buddy.an(); // Phương thức đã ghi đè
buddy.ngu(); // Kế thừa từ DongVat
buddy.sua(); // Phương thức riêng của Cho
System.out.println("---");
// Tạo đối tượng Meo
Meo mun = new Meo("Mun", 2, "Trắng");
mun.hienThiThongTin(); // Kế thừa từ DongVat
mun.an(); // Phương thức đã ghi đè
mun.ngu(); // Kế thừa từ DongVat
mun.keu(); // Phương thức riêng của Meo
System.out.println("---");
// Tính đa hình (Polymorphism):
// Một đối tượng lớp con có thể được gán cho biến kiểu lớp cha
DongVat pet1 = new Cho("Mực", 4, "Phú Quốc");
DongVat pet2 = new Meo("Miu", 1, "Vàng");
System.out.println("Thử nghiệm đa hình:");
pet1.an(); // Gọi phương thức an() của Cho
pet2.an(); // Gọi phương thức an() của Meo
// pet1.sua(); // Lỗi! Biến pet1 kiểu DongVat không biết sua()
}
}
Giải thích nhanh:
super(ten, tuoi);: Dòng này trong constructor của lớp con dùng để gọi constructor của lớp cha. Bắt buộc phải là dòng đầu tiên trong constructor của lớp con nếu lớp cha có constructor có tham số.@Override: Đây là một annotation (chú thích) cho biết bạn đang ghi đè (thay đổi cách hoạt động) một phương thức từ lớp cha. Nó giúp compiler kiểm tra xem bạn có ghi đè đúng không, tránh sai sót. Cứ dùng đi, nó "ngầu" và an toàn.super.hienThiThongTin();: Trong phương thức ghi đè, bạn vẫn có thể gọi lại phương thức gốc của lớp cha nếu muốn tái sử dụng một phần logic của nó.

Mẹo và Best Practices từ anh Creyt (đừng bỏ qua!)
- "Is-a" Relationship là chìa khóa: Chỉ dùng
extendskhi có mối quan hệ "là một". MộtXeĐạplà mộtPhươngTiện, nhưng mộtBánhXethì không phải là mộtPhươngTiện(nó là một bộ phận củaPhươngTiện). Đừng nhầm lẫn giữa "là một" và "có một" (has-a). - Đừng "đẻ" quá nhiều tầng: Cây gia phả càng dài, càng khó quản lý. Tương tự, nếu bạn có một chuỗi kế thừa quá sâu (ví dụ:
A extends B extends C extends D...), code sẽ rất khó đọc, khó bảo trì và dễ gây ra "side effect" không mong muốn. Thường thì 2-3 tầng là đẹp, hơn nữa thì nên xem xét lại. - Ưu tiên Composition over Inheritance (Thành phần hơn Kế thừa): Đây là một "kim chỉ nam" trong OOP. Nếu bạn thấy mối quan hệ là "có một" (has-a), hãy dùng thành phần (composition) thay vì kế thừa. Ví dụ: một
ÔTôcó mộtĐộngCơ, chứÔTôkhôngextends ĐộngCơ. Anh em GenZ nên tìm hiểu thêm về Composition, nó là "vũ khí bí mật" của các pro coder đấy. - Sử dụng
protectedcẩn thận: Các thành viênprotectedcủa lớp cha sẽ được kế thừa và truy cập trực tiếp bởi lớp con. Cònprivatethì "bất khả xâm phạm" từ lớp con. Hãy cân nhắc kỹ quyền truy cập.
Ứng dụng thực tế: extends có ở đâu ngoài đời?
Nói suông thì khó tin đúng không? extends xuất hiện khắp nơi trong các ứng dụng bạn dùng hàng ngày:
- Android App Development: Hầu hết các Activity, Fragment, View bạn tạo đều
extendstừ các lớp cơ sở của Android SDK (ví dụ:MainActivity extends AppCompatActivity). Các lớp này cung cấp sẵn rất nhiều chức năng cơ bản, bạn chỉ việc "kế thừa" và tùy chỉnh. - Java Swing/AWT (UI Frameworks): Khi bạn tạo một nút bấm (
JButton), một khung cửa sổ (JFrame), chúng đềuextendstừ các lớp UI cơ bản nhưJComponenthayWindow, mang theo các đặc tính và hành vi chung của một phần tử giao diện. - Các Framework Backend (Spring, Hibernate): Bạn sẽ thường xuyên thấy các lớp controller, service, repository
extendstừ các lớp cơ sở của framework để có được các tính năng chung như xử lý request, quản lý transaction, v.v. - Game Engines: Trong các game, thường có một lớp
GameObjectcơ bản, sau đó các nhân vật (Player), kẻ thù (Enemy), vật phẩm (Item) đềuextendstừGameObjectđể có các thuộc tính chung như vị trí, vận tốc, khả năng va chạm.
Thử nghiệm đã từng và lời khuyên của Creyt
Anh Creyt đã từng "vọc" đủ trò với extends. Hồi mới học, anh cũng từng cố gắng xây dựng một cây kế thừa "khủng khiếp" với hàng chục tầng, nghĩ là code sẽ "xịn xò". Kết quả là sau này sửa một lỗi nhỏ thôi cũng phải "đào bới" cả cái cây, mệt mỏi và dễ gây bug kinh khủng. Đó là bài học xương máu về việc "đừng lạm dụng kế thừa".
Nên dùng extends khi nào?
- Khi bạn muốn tạo các phiên bản chuyên biệt của một đối tượng chung: Như ví dụ
DongVatvàCho,Meo. - Khi bạn muốn tái sử dụng một tập hợp lớn các phương thức và thuộc tính mà không cần thay đổi chúng quá nhiều: Ví dụ, các lớp tiện ích (
Utility classes) có thể được kế thừa để thêm các phương thức chuyên biệt hơn. - Khi bạn cần áp dụng tính đa hình (Polymorphism): Để có thể xử lý các đối tượng con thông qua tham chiếu của lớp cha, giúp code linh hoạt và dễ mở rộng hơn (như ví dụ
DongVat pet1 = new Cho(...)).
Tóm lại: extends là một công cụ cực mạnh trong OOP Java, giúp bạn xây dựng các hệ thống có cấu trúc, linh hoạt và tái sử dụng code hiệu quả. Tuy nhiên, hãy dùng nó một cách thông minh, đúng chỗ, đừng biến nó thành "con dao hai lưỡi" nhé các GenZ. Hãy nhớ câu thần chú "Is-a" và "Composition over Inheritance"! Chúc các bạn code "mượt" như lướt TikTok!
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é!