Chào mừng các bạn đã đến với bài học Java số 35, bài học về lớp vô danh (anonymous class). Đâ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.
Bài học hôm nay chúng ta sẽ đến với một cách sử dụng lớp và đối tượng bên trong Java một cách thú vị hơn. Thông qua bài học bạn sẽ biết được rằng, không phải lúc nào cũng cứ phải khai báo một lớp rõ ràng rồi sử dụng nó thông qua các khai báo đối tượng từ lớp đó. Bạn có thể tạo ra đối tượng thoải mái từ một lớp mà chẳng ai biết là đối tượng đó thuộc lớp nào cả.
Sau bài học, bạn sẽ biết cách sử dụng lớp trong Java một cách linh động hơn, ngắn gọn hơn, và nếu nhìn quen mắt thì sẽ còn thấy dễ hiểu hơn đấy. Mời các bạn cùng đến với bài học hôm nay.
Lớp Vô Danh Là Gì?
Lớp Vô Danh, tiếng Anh gọi là Anonymous Class, hay nhiều khi được gọi với cái tên đầy đủ hơn là Anonymous Inner Class.
Tất nhiên một lớp được gọi là Vô Danh thì có nghĩa là nó sẽ không có cái tên cụ thể. Lớp Vô Danh sẽ gắn liền với kế thừa (bao gồm cả kế thừa từ một lớp cha bình thường hoặc lớp cha trừu tượng), và gắn liền với interface nữa. Và bởi vì lớp Vô Danh còn có cái tên Anonymous Inner Class, nên nó cũng có liên quan chút đỉnh đến kiến thức về lớp lồng đấy nhé.
Vậy chúng ta cùng nhau tìm hiểu thực sự lớp Vô Danh là gì mà lại dính liếu đến nhiều kiến thức đến như vậy.
Nhận Diện Lớp Vô Danh
Bạn có thể xem mục này đang nói đến cú pháp của một lớp Vô Danh. Nó sẽ giúp bạn nhận diện đâu là một lớp được khai báo bình thường, và đâu là một lớp Vô Danh. Và vì các lớp Vô Danh không có một điểm chung nhất định, nên chúng ta không nói đến cú pháp, mà chỉ nói đến sự nhận diện thôi.
Bạn đều biết, để tạo ra một lớp nào đó, bạn phải khai báo lớp đó với từ khóa class, hiển nhiên, sau đó, bạn phải đặt tên cho nó, như những gì bạn được học ở các bài đầu tiên về OOP vậy.
class tên_lớp { các_thuộc_tính; các_phương_thức; }
Từ khai báo lớp trên, bạn sẽ tạo ra các đối tượng của lớp với từ khóa new sau này.
Còn với các lớp Vô Danh, bạn sẽ không cần phải khai báo một lớp nào, mà vẫn có thể tạo ra các đối tượng của nó. Nghe qua có vẻ mơ hồ, không có lớp thì đối tượng tạo ra sẽ dựa vào đâu.
Trước hết, mời bạn xem “cú pháp” sau của một lớp Vô Danh. Qua cách viết sau đây bạn sẽ tạo ra một đối tượng có tên nhanVien, vậy đối tượng này sẽ là đối tượng được tạo ra từ lớp nào, có phải là lớp NhanVien?
NhanVien nhanVien = new NhanVien() { các_thuộc_tính; các_phương_thức; }
Mình xin nói trước rằng, lớp NhanVien như đoạn code trên không phải là một lớp Vô Danh, lớp NhanVien sẽ được bạn khai báo một cách bình thường ở đâu đó. Lớp Vô Danh với ví dụ trên chính là cái lớp nào đấy được định nghĩa bằng khối lệnh với các_thuộc_tính và các_phương_thức, lớp Vô Danh này chính là lớp con của lớp NhanVien, rồi lớp Vô Danh này được gán vào cho đối tượng nhanVien. Sau này bạn có thể sử dụng thoải mái đối tượng nhanVien này.
Chốt mọi thứ lại, nếu hỏi rằng nhanVien được tạo ra từ lớp nào, thì câu trả lời chỉ có thể là Vô Danh.
Nếu bạn còn chưa hiểu rõ lắm về khái niệm Vô Danh, thì có thể đọc tiếp bên dưới, mình sẽ trình bày cụ thể hơn nhé.
Khi Nào Nên Sử Dụng Lớp Vô Danh?
Như các ý trên đây, mình xin tổng hợp lại. Lớp Vô Danh thường được dùng khi bạn không muốn phải khai báo cụ thể lớp con của một lớp nào đó (bao gồm cả lớp trừu tượng và lớp bình thường mà bạn biết), kể cả khi bạn không muốn khai báo cụ thể lớp triển khai của một interface nào đó, mà vẫn muốn sử dụng các đối tượng của chúng.
Nói vòng vo cũng chỉ bấy nhiêu ý trên thôi. Sau đây mình xin đưa ra một ví dụ về sử dụng lớp Vô Danh để bạn dễ hiểu hơn.
Thực Hành Xây Dựng Lớp Vô Danh
Bài thực hành này giúp bạn hiểu rõ thế nào là một lớp Vô Danh, thông qua một interface có sẵn có tên HinhHoc.
Mình xin phép lấy lại bài thực hành khai báo một interface mà bạn biết để tạo ra một interface với cái tên HinhHoc trước, code này chưa liên quan gì đến lớp Vô Danh cả nhé.
interface HinhHoc { float PI = 3.14f; void nhapBanKinh(float banKinh); float tinhChuVi(); float tinhDienTich(); void xuatThongTin(); }
Với interface HinhHoc trên đây, nếu sử dụng theo cách thông thường, bạn có thể khai báo một lớp “Hữu Danh” có tên HinhTron. Lớp HinhTron này triển khai các phương thức trừu tượng từ interface HinhHoc như sau.
public class HinhTron implements HinhHoc { protected String ten; protected float banKinh; // Constructor public HinhTron(float banKinh) { this.ten = "Hình Tròn"; this.banKinh = banKinh; } @Override public void nhapBanKinh(float banKinh) { this.banKinh = banKinh; } @Override public float tinhChuVi() { return 2 * PI * banKinh; } @Override public float tinhDienTich() { return PI * banKinh * banKinh; } @Override public void xuatThongTin() { System.out.println(ten); System.out.println("Chu vi: " + tinhChuVi()); System.out.println("Diện tích: " + tinhDienTich()); } }
Tiếp tục, nếu muốn sử dụng lớp HinhTron trên đây, thì chúng ta sẽ khai báo đối tượng và gọi đến như bình thường.
public static void main(String[] args) { HinhTron hinhTron = new HinhTron(10); hinhTron.xuatThongTin(); }
Việc bạn khai báo lớp có tên là HinhTron, thì coi như bạn đã khai báo một lớp có tên hẳn hoi rồi.
Giờ chúng ta đến với cách sử dụng lớp Vô Danh. Đầu tiên, bạn chẳng cần phải khai báo lớp HinhTron nào cả, nhưng bạn vẫn cần interface HinhHoc để làm lớp cha cơ bản. Như vậy, bạn hãy quên đi câu lệnh khai báo lớp HinhTron trên kia, mà hãy sử dụng ngay và luôn đối tượng được khởi tạo ở code sau.
public static void main(String[] args) { HinhHoc hinhGiDo = new HinhHoc() { protected float banKinh; @Override public void nhapBanKinh(float banKinh) { this.banKinh = banKinh; } @Override public float tinhChuVi() { return 2 * PI * banKinh; } @Override public float tinhDienTich() { return PI * banKinh * banKinh; } @Override public void xuatThongTin() { System.out.println("Hình Vô Danh"); System.out.println("Chu vi: " + tinhChuVi()); System.out.println("Diện tích: " + tinhDienTich()); } }; hinhGiDo.nhapBanKinh(10); hinhGiDo.xuatThongTin(); }
Với code trên đây, bạn phải chú ý nhìn kỹ, hiệu ứng phụ của code sẽ làm hoa mắt với những ai chưa nhìn quen.
Bạn xem, với khai báo HinhHoc hinhGiDo = new HinhHoc{…}, khai báo này rõ ràng sẽ tạo ra một đối tượng hinhGiDo, nhưng bạn đã biết hinhGiDo không phải được tạo ra từ HinhHoc đúng không nào. Rất dễ để giải thích, vì HinhHoc là interface, mà bạn đã biết một interface không thể dùng để tạo ra một đối tượng!!! Vậy hinhGiDo là một đối tượng được tạo ra từ một lớp Vô Danh, mà lớp Vô Danh này đã triển khai đầy đủ interface HinhHoc thông qua khối lệnh của nó. Các dòng code sau đó của phương thức trên là các sử dụng đối tượng hinhGiDo để nhập thông tin và xuất thông tin ra console để cho ra kết quả tương tự như khi không sử dụng lớp Vô Danh trên kia. Bạn thử thực thi nhé.
Phân Loại Lớp Vô Danh
Bạn đã làm quen và đã hiểu rõ về lớp Vô Danh rồi đúng không nào. Để có thể dễ dàng phát hiện và áp dụng lớp Vô Danh vào thực tế, mình xin tổng hợp phân loại của lớp Vô Danh. Một số thông tin của mục này có thể sẽ được bắt gặp ở các bài học sau, hoặc ở các bài học Android, bạn có thể xem trước, sau này khi sử dụng lại thông tin của lớp Vô Danh, mình sẽ nhắc lại cho bạn nhớ.
Lớp Vô Danh Được Tạo Ra Thông Qua Kế Thừa Từ Lớp Khác
Nếu bạn có một lớp nào đó, dù cho đó là lớp thường hay lớp trừu tượng. Thì thay vì khai báo một lớp con của nó với tên tuổi hẳn hoi, bạn có thể khai báo một lớp con Vô Danh. Như sau này bạn cần phải kế thừa lớp Thread từ hệ thống như sau.
public static void main(String[] args) { // Ví dụ về lớp Vô Danh được tạo ra // thông qua kế thừa từ lớp Thread Thread t = new Thread() { @Override public void run() { System.out.println("Bạn có thể thử nghiệm thực thi code này"); } }; t.start(); }
Ví dụ này chắc không khó với bạn. Đối tượng t mà bạn khai báo không phải là đối tượng của lớp Thread, mà chỉ là đối tượng của lớp Vô Danh kế thừa từ Thread mà thôi.
Lớp Vô Danh Được Tạo Ra Thông Qua Triển Khai Từ Interface Khác
Mục này tương tự như ví dụ của bài học trên kia. Khi mà bạn có một interface, thay vì khai báo một triển khai rõ ràng từ interface này, bạn có thể áp dụng lớp Vô Danh.
Ví dụ sau đây sẽ triển khai từ interface có tên Runnable. Bạn cũng sẽ được làm quen với code này ở bài học về đa luồng (multi thread) sau.
public static void main(String[] args) { // Ví dụ về lớp Vô Danh được tạo ra // thông qua triển khai từ Interface Runnable Runnable r = new Runnable() { @Override public void run() { System.out.println("Bạn có thể thử nghiệm thực thi code này"); } }; Thread t = new Thread(r); t.start(); }
Lớp Vô Danh Được Dùng Như Một Tham Số Truyền Vào
Đây là phần mở rộng hơn so với hai cách trên, nếu bạn nào đang lập trình Android, sẽ thấy ứng dụng rất nhiều.
Ví dụ dưới đây cho thấy thay vì truyền r vào phương thức khởi tạo của Thread(), bạn có thể tạo lớp Vô Danh như một tham số thay cho khai báo r.
public static void main(String[] args) { // Ví dụ về lớp Vô Danh được tạo ra // như một tham số truyền vào Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("Bạn có thể thử nghiệm thực thi code này"); } }); t.start(); }
Dưới đây là hình ảnh bổ sung thêm cho ví dụ về việc sử dụng lớp Vô Danh như một tham số truyền vào của dòng code xây dựng ứng dụng TourNote bên các bài học Android. Bạn tham khảo tính hữu dụng của nó nhé.
Các Đặc Điểm Của Lớp Vô Danh
Dưới đây chúng ta sẽ tổng hợp lại một số đặc điểm đáng nhớ của lớp Vô Danh. Và vì lớp Vô Danh sẽ được sử dụng khá nhiều do tính tiện dụng của nó, nên bạn cũng cố gắng ghi nhớ các đặc điểm này nhé.
– Nếu một lớp bình thường có thể triển khai bao nhiêu interface cũng được, thì lớp Vô Danh chỉ có thể triển khai từ duy nhất một interface mà thôi.
– Nếu một lớp bình thường vừa có thể kế thừa từ một lớp nào đó, vừa có thể triển khai từ nhiều interface khác, thì lớp Vô Danh chỉ có thể hoặc là kế thừa hoặc là triển khai một lớp hay một interface khác thôi.
– Với một lớp bình thường, bạn có thể định nghĩa các constructor tùy thích. Nhưng lớp Vô Danh lại không hề có bất kỳ constructor nào. Điều này cũng dễ hiểu, vì constructor buộc phải có tên trùng với tên lớp, mà lớp Vô Danh thì không có tên, nên chẳng bao giờ bạn có thể định nghĩa được constructor cho nó.
– Và vì một lớp Vô Danh được khai báo bên trong lớp khác, nên nó có một đặc điểm giống với lớp Lồng, ở chỗ nó có thể truy cập đến các thành viên của lớp bao của nó.
Trên đây là những kiến thức liên quan đến lớp Vô Danh. Hi vọng đọc xong bài viết này, nhiều bạn sẽ ồ lên rằng thì ra các cách sử dụng các lớp dạng này từ trước đến giờ được gọi với một cái tên như vậy. Vì mình cũng đã từng ồ lên như vậy rồi mà.
Bài Kế Tiếp
Bài sau chúng ta cùng xem qua kiến thức về một loại lớp trong Java có cái tên chung là Wrapper.
Mình thắc mắc tại sao một biến local được khai báo ở lớp ngoài khi muốn truy xuất ở lớp vô danh thì phải thêm final cho biến đó. Trong khi biến global thì không cần?
Chào bạn. Theo mình được biết, thì khi truyền một biến local vào trong lớp vô danh, thì bên trong lớp vô danh ấy, biến local ấy không còn được dùng nữa mà một bản sao của biến sẽ được tạo ra và sử dụng. Còn tại sao bên trong lớp vô danh lại phải tạo một bản sao của biến local, là vì Java quy định vậy để tránh trình biên dịch tạo ra nhiều biến thể của biến local ban đầu ấy. Và vì là một bản sao của biến local ban đầu được dùng, nên để cho biến local và bản sao của nó luôn được giống nhau về mặt giá trị, một khai báo final khi này bắt buộc được thêm vào, nhằm đảm bảo bản sao của biến local đó sẽ không bị thay đổi giá trị bên trong lớp vô danh đó. Còn đối với biến global, thì không có chuyện bản sao của biến dạng này được tạo ra bên trong lớp vô danh, bạn có thể sử dụng hoặc thay đổi giá trị của biến global thoải mái, nên không cần khai báo final cho biến dạng global này.
Cảm ơn câu trả lời của bạn.
Bài trên sao rối vậy nhỉ? bạn nói “Bạn xem, với khai báo HinhHoc hinhGiDo = new HinhHoc{…}, khai báo này rõ ràng sẽ tạo ra một đối tượng hinhGiDo, nhưng bạn đã biết hinhGiDo không phải được tạo ra từ HinhHoc đúng không nào”, phần code nó ghi rành rành new HinhHoc() thì nghĩa là phải khởi tạo từ Class HìnhHoc chứ làm sao có chuyện “ko khởi tạo từ HinhHoc”.
Chào bạn, bản chất của lớp vô danh là vậy í bạn. Nó làm cho bạn tưởng bạn đã tạo ra một đối tượng của lớp, nhưng thực chất nó chỉ mượn danh của lớp đó để tạo ra một lớp khác mà thôi. Nếu bạn viết HinhHoc hinhGiDo = new HinhHoc(); <-- Có chấm phẩy kết thúc khai báo <-- thì hinhGiDo là đối tượng được tạo từ HinhHoc, không hề bàn cãi. Còn nếu bạn viết HinhHoc hinhGiDo = new HinhHoc() {...} <-- Bạn có mở ngoặc nhọn để khai báo nội dung cho lớp này, thì hinhGiDo là đối tượng được tạo ra từ danh tính của HinhHoc để tạo ra một lớp mới, vì rõ ràng bạn đang khai báo gì đó bên trong {...} chứ bạn có sử dụng chính các thành viên của HinhHoc đâu mà nói rằng hinhGiDo chính là HinhHoc. Chắc cũng rối như bài viết bạn nhỉ? 😀
thứ nhất, nếu nói theo bác thì đó không phải là khởi tạo từ class hinhHoc mà khởi tạo từ class con của class hinhHoc, bác không hiểu bài viết rồi, tôi thấy bài viết cực kỳ tâm huyết, đi vào trọng tâm vấn đề luôn ý
thứ hai, đúng theo bài viết thì hinhHoc nó là interface chứ không phải class bác nhé, nên không thể khởi tạo đối tượng trực tiếp từ interface đó được.
Cái thứ 2 là cái phần code sau đó ở Hàm main() thường người ta code ngắn gọn nhất có thể, sao bạn code dài dằng dặc y hệt Class HinhTron rồi cuối cùng inThongTin(), thế thì làm nguyên 1 main() tính từ đầu đến cuối cho xong, cần gì interface rồi lớp vô danh nữa? Bạn giảng rất dễ hiểu nhưng phần Vo Danh này giảng rối rắm quá!
Mình đồng ý với ý kiến này của bạn. Phần thực hành của lớp vô danh mình đang cố gắng ép làm sao để dùng đến lớp vô danh thôi, nên nó hơi gượng gạo, và dài dòng. Đôi khi lý thuyết với thực tế nó khác nhau ở cái chỗ gượng gạo này. Sang phần “Phân Loại Lớp Vô Danh” mình mới nói đến các công dụng thực tế của chúng, bạn sẽ thấy tầm quan trọng và ứng dụng của chúng như thế nào.
em là người mới học, và em cũng mượng tượng sơ sơ rồi. em ô lên 1 tiếng là từ trước đến nay, interface, abstract không dùng để khởi tạo 1 đối tượng. mà giờ có thể dùng để một lớp vô danh tạo 1 đối tượng. nói chung em cũng đang mơ hồ vì chưa trải qua các dự án, nhưng mà vẫn ồ lên vì đây là một điều mới mẻ với em. cảm ơn anh vì những bài viết rất tâm huyết
Cảm ơn bạn đã chia sẻ
Cảm ơn anh rất nhiều. E mới học java gần đây, thực sự lúc đọc code e cũng có thấy qua cái anonymous class này nhưng e ko hề biết nó là cái gì cả. Nhưng giờ e đã hiểu rồi. A giảng rất dễ hiểu phần thực hành cũng rất trực quan phù hợp vs beginner ^^
Khó hiểu quá
một lớp bình thường vừa có thể kế thừa từ một lớp nào đó, vừa có thể triển khai từ nhiều interface khác… tức là 1 class vừa có thể kế thừa từ class, đồng thời triển khai từ interface??
Đúng vậy bạn
đúng v :>
b nên gọi thẳng là anonymus class luôn,chứ cứ vô danh vô danh hơi lộn xộn
– Với một lớp bình thường, bạn có thể định nghĩa các constructor tùy thích. Nhưng lớp Vô Danh lại không hề có bất kỳ constructor nào. Điều này cũng dễ hiểu, vì constructor buộc phải có tên trùng với tên lớp, mà lớp Vô Danh thì không có tên, nên chẳng bao giờ bạn có thể định nghĩa được constructor cho nó.
??? sao mình vẫn khai báo đc nhỉ???
kiểu gì z tar?
Sao được bạn ;)) Nếu được bạn có thể cho xem đoạn code bạn đã dùng được không
:))) Nó đã không có tên lớp rồi thì bạn tạo constructor kiểu gì 😀
Ta có thể tạo một class vô danh triển khai nhiều giao diện bằng cách tạo một giao diện đóng vai trò trung gian kế thừa tất cả những giao diện mà ta muốn lớp vô danh triển khai sau đó tạo lớp vô danh triển khai giao diện trung gian này. Bằng cách này ta sẽ khắc phục đặc điểm “một lớp vô danh chỉ có thể thừa kế một interface”. Cách làm vậy có đúng không? tôi nghĩ ra ngay cách này khi đọc đến đặc điểm của lớp vô danh :v