Được chỉnh sửa ngày 21/12/2022.
Chào mừng các bạn đã đến với bài học Java số 28: từ khóa static. Đâ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ó thể nói, cho đến bài học OOP hôm nay, đã có ngày càng nhiều từ khóa được lần lượt giới thiệu đến các bạn. Để mình giúp các bạn ôn lại một chút các từ khóa và tên gọi quan trọng mà chúng ta đã xem qua.
- Từ khóa extends – Bạn bắt đầu làm quen với từ khóa này khi thể hiện sự kế thừa.
- Từ khóa this – Giúp tham chiếu đến các thành phần bên trong lớp.
- Từ khóa super – Giúp tham chiếu đến các thành phần của lớp cha gần nhất.
- Annotation @Overriding – Thể hiện phương thức đang định nghĩa là phương thức phủ quyết (hay ghi đè, hay overriding).
- Từ khóa Object – Dùng để chỉ lớp Object, lớp cha nhất của tất cả các lớp trong Java.
- Các từ khóa private/protected/public – Dùng khi định nghĩa khả năng truy cập vào các thành phần lớp.
- Từ khóa final – Giúp định nghĩa hằng số, hoặc ngăn chặn sự overriding bên trong OOP.
- Tên gọi getter và setter – Nhằm ám chỉ đến sự gói ghém dữ liệu thông qua các phương thức get/set cho thuộc tính private.
Vậy thì hôm nay, chúng ta lại tiếp tục tìm hiểu thêm một từ khóa mới, từ khóa static, xem từ khóa này là gì, và nó có công dụng gì nữa nhé.
Khái Niệm static
Có thể nói, một trong những nghĩa tiếng Việt trong ngôn ngữ lập trình gây khó hiểu nhất là đây. Static – Dịch ra là tĩnh. Không hiểu gì cả.
Thực ra thì từ khóa static sẽ được áp dụng khi bạn khai báo các thành phần của lớp như bên dưới mình sẽ trình bày rõ. Nó mang tác dụng chính đối với sự quản lý về mặt bộ nhớ. Cụ thể là, các thành phần static sẽ thuộc về quản lý bộ nhớ của lớp, chứ không thuộc về quản lý của thể hiện lớp (hay có thể hiểu là đối tượng).
Vẫn chưa hiểu!.
Mình mời bạn nhìn vào ví dụ sau, có thể qua ví dụ bạn vẫn chưa nắm rõ công dụng mà từ khóa static mang lại, nhưng hãy xem nó tác động như thế nào đến việc quản lý các thành phần bên trong lớp mà mình có nói trên đây. Bạn hãy chú ý các thành phần static và không static trong lớp ToaDo.
public class ToaDo { public static String thongTin; public int x; public int y; }
Vậy như mình có nói. Thuộc tính thongTin là static, và nó sẽ thuộc quyền quản lý của lớp ToaDo. Các thuộc tính x, y là không static, sẽ thuộc quyền quản lý của các thể hiện, hay các đối tượng được khai báo từ ToaDo.
Vấn đề sẽ rõ ràng hơn khi bạn dùng đến các thành phần static này của ToaDo ở một lớp khác.
public class MainClass { public static void main(String[] args) { // Các thuộc tính x, y này chỉ được truy xuất đến thông qua thể hiện toaDo1 của lớp ToaDo ToaDo toaDo1 = new ToaDo(); toaDo1.x = 10; toaDo1.y = 20; // Các thuộc tính x, y này chỉ được truy xuất đến thông qua thể hiện toaDo2 của lớp ToaDo ToaDo toaDo2 = new ToaDo(); toaDo2.x = 5; toaDo2.y = 6; // Thuộc tính thongTin lại được truy xuất đến thông qua lớp ToaDo ToaDo.thongTin = "Lưu tọa độ các hình học"; } }
Bạn có thấy rằng, các thuộc tính x, y, cũng như bao thành phần không static khác mà bạn từng biết, đều phải được gọi thông qua một thể hiện của lớp, như toaDo1, toaDo2 ở ví dụ. Còn thuộc tính thongTin lại được truy cập trực tiếp thông qua lớp ToaDo, mà không cần bất cứ một thể hiện nào cả.
Đến đây thì bạn đã hiểu được thành phần static là gì đúng không nào. Chắc bạn cũng “hườm hườm” ngẫm được tại sao nó lại có nghĩa là tĩnh.
Tiếp theo chúng ta cùng tìm hiểu, từ khóa static sinh ra để làm gì.
Công Dụng Của Từ Khóa static
Như một số thông tin mà mình đã trình bày trên đây, bạn có thể thấy rằng, với việc gọi đến thành phần của lớp thông qua tên lớp, mà không cần phải khởi tạo đối tượng của lớp đó, giúp cho các thành phần được khai báo static sẽ dễ dàng truy xuất hơn.
Đặc biệt hơn cả là, các thành phần được khai báo static này có thể được dùng chung bởi các lớp khác trong ứng dụng. Nó mang ý nghĩa toàn cục trong cả ứng dụng. Bạn cứ thử nhìn vào ví dụ trên, bạn đã thử gán giá trị cho static thongTin trong ToaDo bằng dòng lệnh ToaDo.thongTin = "Lưu tọa độ các hình học";
, thì ở một chỗ khác, trong lớp khác chẳng hạn, bạn có thể lấy giá trị ToaDo.thongTin này ra dùng, hoặc gán lại cho nó một giá trị mới. Như vậy thành phần static sẽ được chia sẻ, được dùng chung, bởi bất kỳ chỗ nào, trong lớp đó hoặc bên ngoài lớp (miễn là thành phần đó đừng khai báo là private).
Nhưng việc các thành phần của lớp được truy cập và sửa đổi thoải mái như thế này lại là con dao hai lưỡi. Nếu bạn lạm dụng từ khóa static, bạn sẽ vô tình làm phá vỡ nguyên tắc của OOP. Vốn dĩ OOP ra đời là để phân chia đặc điểm và trách nhiệm cho từng đối tượng, để dễ quản lý. Thì từ khóa static lại mang các tính chất vốn dĩ thuộc trách nhiệm của đối tượng nào đó, ra dùng chung một cách đại trà. Do đó, mình khuyên là bạn nên thật cân nhắc khi khai báo static cho bất cứ thành phần nào của lớp.
Vậy từ khóa static thực chất được dùng ở đâu? Mình mời các bạn đọc qua phần dưới đây. Ở từng công dụng cụ thể của từ khóa static, mình sẽ tìm những ví dụ thực tế nhất mà từ khóa static mang lại trong quá trình xây dựng ứng dụng.
Khai Báo Thuộc Tính static
Có lẽ không cần phải nói nhiều nữa. Thuộc tính static là thuộc tính có khai báo từ khóa static. Chúng được sử dụng như thế nào thì chúng ta cùng xem qua các ví dụ dưới đây, bạn sẽ hiểu rõ thôi.
Bài Thực Hành Số 1
Chúng ta cùng quay lại ví dụ về các thể loại hình học. Ví dụ như chúng ta có HinhHoc là lớp cha của hai lớp HinhTron và HinhChuNhat. Và yêu cầu của bài thực hành hôm nay là, mỗi khi chúng ta tạo ra một đối tượng hình học, thì có một biến đếm tự động tăng lên cho biết số lượng hình được tạo ra trong một chương trình.
Nào cùng xem qua lớp HinhHoc, giả sử chúng ta không quan tâm đến các dòng code tính toán khác, chỉ chú tâm vào biếm dem được set static bên trong lớp này.
public class HinhHoc { public static int dem = 0; public HinhHoc() { dem++; } // Các dòng code khác // ... }
Sau đó, cứ mỗi constructor của lớp con, chúng ta đều gọi đến lớp cha gần nhất của nó thông qua phương thức super(). Việc gọi này sẽ làm tăng biến dem này lên ở constructor của lớp cha.
public class HinhTron extends HinhHoc { // Constructor public HinhTron() { super(); } // Các code tính toán chu vi, diện tích // ... }
public class HinhChuNhat extends HinhHoc { // Constructor public HinhChuNhat() { super(); } // Các code tính toán chu vi, diện tích // ... }
Và rồi ở hàm main(), bạn hãy thử khai báo các đối tượng của HinhTron và HinhChuNhat thoải mái, rồi hãy in biến dem ra console xem sao nhé.
public class MainClass { public static void main(String[] args) { // Tạo các thể hiện của các lớp HinhHoc hinhHoc = new HinhHoc(); HinhTron hinhTron1 = new HinhTron(); HinhTron hinhTron2 = new HinhTron(); HinhChuNhat hinhChuNhat = new HinhChuNhat(); System.out.println("Có tất cả " + HinhHoc.dem + " hình trong ứng dụng."); } }
Kết quả in ra có 4 hình trong ứng dụng
. Bạn thấy biến dem được khai báo static sẽ được dùng chung như thế nào rồi đúng không nào.
Bài Thực Hành Số 2
Có khi nào bạn từng thắc mắc rằng, sẽ làm sao nếu như mong muốn ứng dụng có các giá trị cố định dùng chung, nó mang ý nghĩa là các giá trị cấu hình cho ứng dụng. Chẳng hạn như khai báo đường dẫn tĩnh để lưu file này, hay khai báo đường dẫn đến một trang web này, hay đường dẫn API này,…. Thì tất cả các nhu cầu về cấu hình tĩnh đó, bạn có thể gom chung chúng vào một file và khai báo các thuộc tính static kết hợp với final cho nó.
Tiếp tục với ví dụ về việc đếm các hình học trên đây. Giả sử yêu cầu của bài toán lúc này đặt ra là bạn không được khai báo quá 5 hình. Nếu ứng dụng có quá 5 hình, thay vì câu lệnh in ra số lượng hình học ở trên đây, bạn có thể in ra câu thông báo nào đó. Trong trường hợp này, số lượng tối đa 5 hình trong ràng buộc được xem như là một cấu hình tĩnh của ứng dụng.
Đầu tiên, chúng ta sẽ cần phải tạo một lớp chỉ chuyên cho việc cấu hình thôi. Mình đặt tên lớp này là Configs.
public class Configs { // Cấu hình số lượng hình học public static final int SO_LUONG_HINH_TOI_THIEU = 0; public static final int SO_LUONG_HINH_TOI_DA = 5; // Các cấu hình khác nếu có // public static final xxx xxx // public static final xxx xxx // ... }
Quay lại main(), bạn hãy thử khai báo nhiều nhiều các đối tượng hình học xem nào. Sau đây là các kiểm tra ràng buộc dựa trên các giá trị cấu hình tĩnh lấy từ lớp Configs.
public class MainClass { public static void main(String[] args) { // Tạo các thể hiện của các lớp HinhHoc hinhHoc1 = new HinhHoc(); HinhHoc hinhHoc2 = new HinhHoc(); HinhTron hinhTron1 = new HinhTron(); HinhTron hinhTron2 = new HinhTron(); HinhChuNhat hinhChuNhat1 = new HinhChuNhat(); HinhChuNhat hinhChuNhat2 = new HinhChuNhat(); if (HinhHoc.dem > Configs.SO_LUONG_HINH_TOI_DA) { System.out.println("Bạn đã khai báo vượt số lượng hình học cho phép!"); System.out.println("Số lượng hình học tối thiểu là: " + Configs.SO_LUONG_HINH_TOI_THIEU); System.out.println("Số lượng hình học tối đa là: " + Configs.SO_LUONG_HINH_TOI_DA); } else { System.out.println("Có tất cả " + HinhHoc.dem + " hình trong ứng dụng."); } } }
Giờ thì bạn có thể chạy thử ứng dụng lên kiểm chứng nhé.
Khai Báo Phương Thức static
Phương thức static được sử dụng với đặc điểm và mục đích tương tự như thuộc tính static trên kia. Đó là.
- Các phương thức static vẫn thuộc quản lý của lớp chứ không phải của thể hiện lớp.
- Một phương thức được set là static khi chúng được dùng chung bởi tất cả các đối tượng bên trong ứng dụng. Như các phương thức về kiểm tra tính đúng đắn của giá trị nào đó, phương thức về chuyển đổi tiền tệ, chuyển đổi đơn vị, phương thức về lưu trữ giữ liệu xuống bộ nhớ, phương thức kết nối với server,…
Tuy nhiên có một lưu ý hết sức đặc biệt trong Java là, tất cả các phương thức static chỉ có thể gọi đến các thuộc tính được khai báo là static mà thôi. Điều này được dễ dàng kiểm chứng khi rất lâu rồi, ở bài 8, mình buộc phải khai báo biến static name khi biến này để ở ngoài main(), và vì main() được khai báo là static (bạn đã biết là main() bắt buộc phải khai báo là phương thức static mà đúng không, nguyên nhân là để hệ thống có thể truy xuất bất cứ lúc nào thông qua lớp, chứ không cần khai báo thể hiện của lớp nào đó), nên nếu main() muốn dùng đến name, thì name cũng phải static.
Bài Thực Hành Số 3
Với bài thực hành này, giả sử ứng dụng hình học của chúng ta đòi hỏi khắt khe hơn về mặt nhập liệu và tính toán dựa trên các đơn vị. Và giả sử chúng ta chấp nhận hai đơn vị nhập vào là inch và centimet.
Trước mắt, lớp Configs sẽ phải xây dựng thêm một số giá trị cấu hình tĩnh, và phải thêm các phương thức tĩnh chuyển đổi đơn vị nữa.
public class Configs { // Cấu hình số lượng hình học public static final int SO_LUONG_HINH_TOI_THIEU = 0; public static final int SO_LUONG_HINH_TOI_DA = 5; // Các cấu hình khác public static final float PI = 3.14f; public static final float INCH_CM = 2.54f; // 1 inch = 2.54 cm public static final int DON_VI_CM = 1; // Đánh dấu ứng dụng đang dùng đơn vị centimet public static final int DON_VI_INC = 2; // Đánh dấu ứng dụng đang dùng đơn vị inch public static int donVi = DON_VI_CM; // Cờ Cho biết đang dùng đơn vị gì // Phương thức static giúp chuyển đổi centimet sang inch public static float ChuyenCentimetSangInch(float cm) { float inch = cm / INCH_CM; return inch; } // Phương thức static giúp chuyển đổi inch sang centimet public static float ChuyenInchSangCentimet(float inch) { float cm = inch * INCH_CM; return cm; } }
Sau đó, ở lớp hình học nào cũng vậy, ví dụ dưới đây mình xây dựng cho lớp HinhTron, lớp này sẽ hỏi người dùng đang dùng đơn vị hình học nào, và sẽ xuất thông tin tương ứng kèm với thông tin chuyển đổi sang đơn vị còn lại dựa vào các cấu hình tĩnh và các phương thức tĩnh trong lớp Configs.
public class HinhTron extends HinhHoc { protected float banKinh; private Scanner scanner; // Constructor public HinhTron() { super(); scanner = new Scanner(System.in); } public void nhapBanKinh() { // Nhập đơn vị tính là centimet hay inch System.out.println("Bạn dùng đơn vị tính nào: "); System.out.println("\t1 - Centimet"); System.out.println("\t2 - inch"); Configs.donVi = scanner.nextInt(); // Sau đó nhập bán kính System.out.print("Hãy nhập vào Bán kính Hình tròn: "); banKinh = scanner.nextFloat(); } public void inThongTin() { if (Configs.donVi == Configs.DON_VI_CM) { System.out.println("Hình tròn có bán kính " + banKinh + " cm"); System.out.println("Tương đương " + Configs.ChuyenCentimetSangInch(banKinh) + " inch"); } else { System.out.println("Hình tròn có bán kính " + banKinh + " inch"); System.out.println("Tương đương " + Configs.ChuyenInchSangCentimet(banKinh) + " cm"); } } }
Phương thức main() được xây dựng đơn giản như sau.
public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(); hinhTron.nhapBanKinh(); hinhTron.inThongTin(); } }
Bạn tự chạy và kiểm chứng kết quả của chương trình nhé.
Kết Luận
Vậy là chúng ta vừa đi qua khái niệm và cách sử dụng từ khóa static trong Java. Bạn có thể thấy rằng các thành phần static thực sự rất dễ dùng đúng không nào. Và lời khuyên của mình vẫn luôn là, bạn hãy cân nhắc, đừng lạm dụng từ khóa static này quá. Nếu không vì mục đích dùng chung như các ví dụ trên đây của mình, thì bạn đừng nên sử dụng đến từ khóa static 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ài Kế Tiếp
Chúng ta sẽ nói qua một chút về nạp chồng phương thức trong OOP, hay còn gọi với cái tên tiếng Anh là method overloading.
Bài viết rất hay hi vọng bác viết nhanh nhanh 1 tí. Tks
Ô kê bạn, mình sẽ cố viết nhanh tí. Hi vọng nếu đợi có lâu tí thì mọi người đừng nản nhé 😀
public class HinhTron extends HinhHoc {
protected float banKinh;
private Scanner scanner;
// Constructor
public HinhTron() {
super();
scanner = new Scanner(System.in);
Ad ơi,cho em hỏi cái phần Scanner ấy; em không rõ lắm anh có thể giải thích giúp em được không
Em cảm ơn
Scanner là một lớp được cung cấp sẵn bởi Java, nó giúp chúng ta “lấy” được các giá trị từ console để dùng vào một số mục đích trong ứng dụng. Mình có nói qua về Scanner này ở bài số 7 rồi bạn, bạn thử xem lại ở link này xem có còn thắc mắc gì thêm không nhé: https://yellowcodebooks.com/2016/11/01/java-bai-7-nhapxuat-tren-console/
Hay lắm anh ạ, cảm ơn anh vì bài viết ạ ^^
Cảm ơn AD rất nhiều ạ, một bài viết chất lượng giúp giải quyết suy nghĩ bế tắc của em <3 <3
Em thấy ở constructor của các class HinhTron hay HinhChuNhat, do chúng nó extends từ HinhHoc nên khi new 1 đối tượng mới từ 2 class này, đều phải gọi Constructor của lớp HinhHoc trước.
Vì vậy, mình không cần gọi super(); như trên, nhưng số lượng hình được tạo ra và lưu ở biến dem vẫn đúng là 4 (như vd trên).
Chuẩn luôn bạn!
public class ToaDo {
public static String thongTin;
public int x;
public int y;
}
Anh ơi cho em hỏi đoạn code này em viết lại chương trình báo lỗi Inner classes cannot have static declaration, phải thêm từ khoá static vào class ToaDo mới hết ạ
Đoạn code này còn nhiều ẩn số lắm, chỉ nhìn vào ToaDo thôi mình cũng chưa đoán biết được nó sẽ sử dụng ở những chỗ nào để dẫn đến lỗi của bạn đưa ra. Bạn hãy chat với mình trên page của Facebook để dễ trao đổi hơn nhé: https://www.facebook.com/yellowcodebooks
ồ trang web bạn viết dễ hiểu ghê, văn phong cũng hài hước nữa. Cảm ơn bạn nhiều nha.
Bài viết rất hay , từ ngữ và văn phong hài hước CỰC KÌ tốt cho việc tiếp thu kiến thức. Chúc bạn/các bạn thành công !