Được chỉnh sửa ngày 22/01/2022.
Chào mừng các bạn đã đến với bài học Java số 34, bài học về lớp lồng trong Java. Đây là bài học trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.
Đọc đến tiêu đề chắc hẳn bạn đã biết nội dung mà bài học hướng đến rồi. Đó chính là kiến thức về việc khai báo một lớp ở bên trong một lớp khác. Đây không phải là quan hệ cha-con gì nhé, do đó nó không phải là kế thừa, nó chỉ là một lớp được khai báo bên trong lớp khác mà thôi. Vậy chúng ta cùng nhau tìm hiểu xem việc lồng các lớp vào với nhau là gì và tại sao lại làm như vậy nhé.
Lớp Lồng Là Gì?
Mình chỉ nhắc lại ý trên kia thôi, lớp lồng chính là việc khai báo một lớp ở bên trong một lớp khác.
Lớp mà chứa các lớp khác bên trong nó người ta gọi là Outer Class, có thể hiểu theo tiếng Việt là lớp Bao.
Còn lớp ở bên trong Outer Class thì được chia ra làm hai loại khác nhau.
- Một là lớp không-static (non-static class), người ta gọi loại này là Inner Class.
- Loại còn lại là lớp static (static class), người ta gọi loại này là Static Nested Class.
Khoan hãy nói đến vai trò của từng loại Inner Class và Static Nested Class, chúng ta hãy đến với cú pháp khai báo của từng loại như sau.
Đây là cú pháp của một Inner Class.
class OuterClass { ... class InnerClass { ... } }
Còn đây là cú pháp của một Static Nested Class.
class OuterClass { ... static class StaticNestedClass { ... } }
Hai cú pháp không khác gì cả ngoài từ khóa static đúng không nào. Qua cú pháp trên, chúng ta có một số ý sau cần ghi nhớ.
- Tuy cú pháp chỉ có một Outer Class chứa một Inner Class hoặc một Static Nested Class bên trong. Nhưng bạn nên biết rằng bên trong một Outer Class có thể chứa nhiều Inner Class, nhiều Static Nested Class, hoặc chứa nhiều cả Inner Class lẫn Static Nested Class.
- Và tuy cú pháp chỉ nói đến lớp lồng, nhưng bạn có thể lồng interface vào trong lớp, hoặc lồng interface vào với nhau, hay lồng lớp vào trong interface đều được nhé.
- Inner class lúc này được xem như một thành phần của Outer Class, chính vì vậy bạn có thể chỉ định cho nó các khả năng truy cập, như private, public, protected, hoặc default (tức là không có khai báo khả năng truy cập gì). Điều này khác với Outer Class hay các lớp mà bạn đã từng làm quen, đều chỉ có thể khai báo public hoặc default (khai báo này chỉ cho phép các lớp cùng trong một package mới có thể nhìn thấy nhau) mà thôi.
Thực Hành Khai Báo & Sử Dụng Inner Class
Bạn nên nhớ là ứng dụng của lớp lồng là khá nhiều, nhưng việc sử dụng chúng hay không là không bắt buộc, và nếu cần sử dụng thì cũng không khó lắm.
Bài thực hành này chúng ta hãy xem việc định nghĩa và sử dụng một Inner Class như thế nào. Xong bài thực hành này chúng ta sẽ nói cụ thể hơn về công dụng của lớp lồng sau nhé.
Code sau khai báo lớp ToaDo được đặt bên trong lớp HinhHoc. Bạn cũng có thể khai báo lớp ToaDo ở ngoài HinhHoc như đã thực hành ở bài nào đấy, nhưng việc để ToaDo vào trong HinhHoc, bạn thấy lớp này có thể sử dụng thuộc tính tenHinh của lớp bao.
public class HinhHoc { public static final float PI = 3.14f; public String tenHinh; public ToaDo toaDo; // Constructor public HinhHoc(int x, int y) { this.tenHinh = "Hình Học"; this.toaDo = new ToaDo(); this.toaDo.x = x; this.toaDo.y = y; } public class ToaDo { int x; int y; public void xuatThongTin() { System.out.println("Hình: " + tenHinh); System.out.println("Tọa độ: x = " + x + "; y = " + y); } } }
Việc lớp lồng sử dụng được thuộc tính của lớp bao sẽ được mình nói rõ hơn ở mục bên dưới các bài thực hành.
Giờ thì bạn tiếp tục xem ở phương thức main() chúng ta khai báo lớp HinhHoc và gọi đến xuatThongTin() của ToaDo như sau, chuyện gì sẽ xảy ra?
public class MainClass { public static void main(String[] args) { HinhHoc hinhHoc = new HinhHoc(10, 20); hinhHoc.toaDo.xuatThongTin(); } }
Chúng ta sẽ nhận được thông tin sau.
Với code trên của phương thức main(), bạn có thể thấy chúng ta khai báo đối tượng của lớp HinhHoc, rồi dùng đến biến toaDo của nó, trên kia viết như này hinhHoc.toaDo.xuatThongTin(). Bạn cũng có thể khai báo đối tượng riêng của lớp ToaDo để dùng cho những lần sau, như sau.
public class MainClass { public static void main(String[] args) { HinhHoc hinhHoc = new HinhHoc(10, 20); HinhHoc.ToaDo toaDo = hinhHoc.new ToaDo(); toaDo.xuatThongTin(); } }
Bạn có thấy cách khai báo lớp ToaDo như code trên đây tuy lạ mà quen đúng không nào. Nhớ lại, ở các bài đầu tiên của OOP, khi mà mình chưa hướng dẫn các bạn có thể tạo nhiều lớp bên trong một project Java, mình đã phải dùng đến lớp lồng, bạn xem code ở bài này. Khi đó cũng có bạn nhanh trí nhìn ra vấn đề và hỏi, đến bây giờ mình mới có dịp được nhắc lại.
Bạn có thể hiểu lúc này ToaDo được xem như là một thành viên của HinhHoc. Chính vì vậy bạn chỉ có thể khởi tạo độc lập ToaDo thông qua đối tượng của HinhHoc là hinhHoc mà thôi.
Khi bạn thực thi dòng code trên kia của main(), bạn sẽ nhận được kết quả hơi khác chút như sau. Bạn có thể tự giải đáp tại sao kết quả lại khác như vậy không?
Thực Hành Khai Báo & Sử Dụng Static Nested Class
Mình cũng sẽ sử dụng kịch bản từ bài thực hành trên, nhưng thay lớp ToaDo bên trong HinhHoc bằng một Static Nested Class. Bạn xem có gì khác biệt không nhé.
public class HinhHoc { public static final float PI = 3.14f; public static String tenHinh; public ToaDo toaDo; // Constructor public HinhHoc(int x, int y) { this.tenHinh = "Hình Học"; this.toaDo = new ToaDo(); this.toaDo.x = x; this.toaDo.y = y; } public static class ToaDo { int x; int y; public void xuatThongTin() { System.out.println("Hình: " + tenHinh); System.out.println("Tọa độ: x = " + x + "; y = " + y); } } }
Bạn có thể thấy, vì ToaDo được định nghĩa là một lớp static, nên nó chỉ có thể truy xuất đến các thuộc tính và phương thức static của lớp bao. Điều này giống với phương thức static mà bạn đã học, khi đó phương thức static cũng chỉ có thể gọi đến các thuộc tính được khai báo static mà thôi.
Với việc khai báo ToaDo như thế này, bạn vẫn gọi đến và sử dụng thông qua đối tượng bao hinhHoc một cách bình thường.
public class MainClass { public static void main(String[] args) { HinhHoc hinhHoc = new HinhHoc(10, 20); hinhHoc.toaDo.xuatThongTin(); } }
Hoặc bạn có thể khai báo độc lập trực tiếp ToaDo hơi khác với khi khai báo ToaDo là Inner Class như trên.
public class MainClass { public static void main(String[] args) { HinhHoc.ToaDo toaDo = new HinhHoc.ToaDo(); toaDo.xuatThongTin(); } }
Bạn thử tự thực thi ứng dụng để xem console in ra gì nhé.
Khi Nào Nên Sử Dụng Lớp Lồng & Công Dụng Của Nó?
Dựa trên những gì chúng ta đã làm quen với lớp lồng trên đây, chúng ta đã có đủ trải nghiệm để tổng hợp lại vài công dụng của nó.
- Nếu bạn có một lớp A chỉ dùng đến một lớp B nào đó. Tức là B không được ai dùng đến cả ngoại trừ A thôi. Thì bạn có thể xem xét tổ chức theo cách B sẽ là lớp lồng vào trong lớp A. Lợi ích của việc này thì bạn có thể xem các ý dưới đây.
- Việc lồng các lớp vào nhau giúp tăng tính gói gém dữ liệu (encapsulation). Chẳng hạn với việc lớp B nằm trong lớp A, thì bạn có thể khai báo B là private, khi đó ngoài A ra không có bất kỳ lớp nào khác có thể biết đến sự tồn tại của B và các giá trị của nó, có thể nói rằng bạn đã “giấu” B khỏi thế giới, ngoại trừ A.
- Ở chiều ngược lại. Lớp lồng B có thể truy cập đến tất cả các thành viên (thuộc tính và phương thức) của lớp bao A, ngay cả khi các thành viên của A được khai báo là private.
- Tất nhiên, khi lồng các lớp vào nhau như vậy, code của bạn sẽ dễ đọc hơn (vì không phải tìm và mở quá nhiều lớp), dẫn đến việc bảo trì cũng dễ dàng hơn.
Bài Tập Số 1
Chúng ta thử trắc nghiệm xem đã hiểu bài chưa thông qua bài tập này.
Giả sử có định nghĩa lớp bao và lớp lồng như sau.
public class OuterClass { public void show() { InnerClass innerClass = new InnerClass(); innerClass.show(); } public class InnerClass { public void show() { System.out.println("Đây là inner class"); } } }
Bạn thử trả lời xem kết quả in ra console sẽ như thế nào với đoạn code ở phương thức main() như sau.
public class MainClass { public static void main(String[] args) { OuterClass outerClass = new OuterClass(); outerClass.show(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.show(); } }
Hãy tham khảo đáp án bên dưới để so sánh với câu trả lời của bạn.
Đây là inner class Đây là inner class
Bài Tập Số 2
Tương tự với bài tập số 1. Giả sử mình có định nghĩa lớp bao và lớp lồng như sau.
public class NgayThangNam { public int ngay, thang, nam; public class GioPhutGiay { public int gio, phut, giay; public void xuatThongTin() { System.out.println("Ngày: " + ngay + "/" + thang + "/" + nam); System.out.println("Giờ: " + gio + ":" + phut + ":" + giay); } } }
Và rồi ở phương thức main() mình gọi đến chúng như sau.
public class MainClass { public static void main(String[] args) { NgayThangNam date = new NgayThangNam(); date.ngay = 31; date.thang = 10; date.nam = 2017; NgayThangNam.GioPhutGiay time = date.new GioPhutGiay(); time.gio = 10; time.phut = 15; time.giay = 30; time.xuatThongTin(); } }
Bạn thử trả lời kết quả in ra console là gì nhé. Và đây là đáp án.
Ngày: 31/10/2017 Giờ: 10:15:30
Kết Luận
Kiến thức về lớp lồng chỉ có vậy thôi. Bạn sẽ quen với việc sử dụng lớp lồng này khi dùng nhiều đến ngôn ngữ Java, hoặc khi bạn đọc code của người khác, hay khi qua lập trình với Android. Tuy nhiên, cũng còn một tí kiến thức liên quan đến lớp lồng này nữa, mình sẽ gộp chung kiến thức còn thiếu này vào bài học về lớp Vô danh (Anonymous) luôn nhé.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài viết nếu thấy thích.
– Comment bên dưới mỗi bài viết nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
– Ủng hộ blog theo hướng dẫn ở thanh bên phải để blog ngày càng phát triển hơn.
Bạn ơi , mình muốn hỏi bài thì mình hỏi qua facebook đc ko , mình muốn hỏi về swing , thứ 6 deadline rồi
Mình nghĩ bạn nên nhanh chóng tìm kiếm lời giải từ các diễn đàn, bằng tiếng Anh là tốt nhất vì có nhiều tài liệu nhất cho bạn. Vì mình chỉ dùng Java với console thôi, đã không dùng swing một thời gian khá lâu rồi nên sẽ không giúp ngay cho bạn được đâu, bạn thông cảm nhé.
Ok , không có sao đâu , tại vì cô mình cảnh dậy gì mà cứ bắt làm
Bạn ơi, bạn giải thích giúp mình sao chổ khai báo đối tượng riêng cho lớp tọa độ in ra (0;0) với!
Bạn để ý xem, với khai báo đầu tiên HinhHoc hinhHoc = new HinhHoc(10, 20) thì bạn đã truyền các biết tọa độ vào bên trong hinhHoc đúng không nào. Nếu in ra lúc này thì sẽ là (10; 20). Nhưng với việc viết tiếp HinhHoc.ToaDo toaDo = hinhHoc.new ToaDo() thì toaDo lúc này với hinhHoc.toaDo là hai đối tượng khác nhau bạn à. Khi này toaDo được khai báo không có các giá trị x, y truyền vào nên nó mới in ra mặc định là (0; 0) nhé.
vậy còn hinhHoc.new có ý nghĩa gì ạ. tại sao mình không dùng được HinhHoc.new ToaDo() ạ?
Với bạn cho mình hỏi, là mình chỉ có thể khai báo đối tượng của inner class bên trong outer class thôi hả. Như với ví dụ trên, mình khai báo ở main đối tượng khác thì báo lỗi:
HinhHoc.ToaDo toaDo2 = new HinhHoc.ToaDo();
toaDo2.xuatThongTin();
À ko, được. Mình lại gõ sai rồi, xin lỗi bạn .-.
Rất hay
cảm ơn bài viết của ad. Rất dễ hiểu ạ.
bài số 1 mình thấy hơi sai: InnerClass innerClass = outerClass.new InnerClass(); –> OuterClass.InnerClass innerClass = outerClass.new InnerClass(); mong admin check ạ
t cx thấy v á, cái đoạn code dùng GioThangNam ko thì compiler báo lỗi, phải dùng là NgayThangNam.GioPhutGiay
Bạn đã phát hiện đúng. Nếu xây dựng project trên InteliJ thì cách viết InnerClass innerClass = outerClass.new InnerClass(); như này IDE sẽ không cho phép do nó không import được lớp InnerClass. Nhưng nếu xây dựng bên Eclipe thì lại được. Tuy nhiên để cả InteliJ và Eclipse đều có thể chạy tốt thì mình sẽ sửa code lại thành OuterClass.InnerClass innerClass = outerClass.new InnerClass(); nhé bạn. Cảm ơn bạn đã phản hồi.
Anh ơi em lên vừa học java vừa học kitlin ko
Mình nghĩ là vẫn được bạn à. Một phần là 2 ngôn ngữ đó cũng có nhiều nét tương đồng, Kotlin như là một nâng cấp mạnh mẽ hơn cho Java. Khi học song song vậy bạn nên chú ý đến việc so sánh giữa 2 ngôn ngữ, việc nắm được chúng khác nhau thế nào tuy có thể sẽ làm chậm việc tìm hiểu 2 ngôn ngữ này của bạn, nhưng nó sẽ mang đến một kiến thức rõ ràng, chắc chắn, và không bị nhầm lẫn với nhau.
Mình thấy ở ví dụ hai, nếu khai báo theo kiểu của ad:
GioPhutGiay time = date.new GioPhutGiay();
thì bắt buộc phần trên phải có import:
import ReviewClass.NgayThangNam.GioPhutGiay;
còn nếu muốn k phải import thì cần phải khai báo theo cấu trúc mà ad giới thiệu bên trên:
NgayThangNam.GioPhutGiay time = date.new GioPhutGiay();
không biết mình hiểu đúng chưa, mong ad bảo thêm ạ
Bạn đã hiểu đúng. Mình đã sừa lại thành NgayThangNam.GioPhutGiay time = date.new GioPhutGiay(); cho đồng bộ hết giữa các bài tập và bài thực hành trong bài học này. Cảm ơn bạn nhé.