Bổ từ (Modifier) và biến lớp/biến tĩnh (Class Variable) trong java
- 01-10-2023
- Toanngo92
- 0 Comments
Java là một ngôn ngữ được đóng gói chặt chẽ. Điều này có nghĩa là không có mã nào có thể được viết bên ngoài lớp. Ngay cả phương thức main() cũng không được phép. Tuy nhiên, ngay cả trong lớp, mã có thể trở nên dễ bị truy cập từ môi trường bên ngoài. Vì vậy, Java cung cấp một tập hợp các bổ từ truy cập như public, private, protected và mặc định giúp hạn chế truy cập vào lớp và thành viên của lớp. Tuy nhiên, đôi khi cần phải hạn chế truy cập hơn nữa đến các thành viên của một lớp để ngăn chặn việc sửa đổi bởi các thực thể không được ủy quyền. Java cung cấp các bổ từ trường (thuộc tính) và bổ từ phương thức cho mục đích này.
Ngoài ra, trong một số trường hợp, có thể cần có một thành viên dữ liệu chung có thể được chia sẻ bởi tất cả các đối tượng của một lớp cũng như các lớp khác. Java cung cấp khái niệm biến và phương thức lớp để giải quyết mục đích này. Các lớp chia sẻ các thuộc tính và hành vi chung cũng có thể được nhóm lại để hiểu tốt hơn về ứng dụng. Java cung cấp các gói (package) có thể được sử dụng để nhóm các lớp liên quan. Hơn nữa, toàn bộ gói có thể được kết hợp vào một tệp đơn gọi là tệp .jar để triển khai trên hệ thống đích.
Mục lục
Bổ từ trường và phương thức
Các bổ từ trường và phương thức là từ khóa được sử dụng để xác định các trường và phương thức cung cấp quyền truy cập kiểm soát cho người dùng. Một số trong số này có thể được sử dụng cùng với các bổ từ truy cập như public và protected. Các bổ từ trường (bổ từ thuộc tính) khác nhau có thể được sử dụng như sau:
- volatile
- native
- transient
- final
Bổ từ volatile:
Bổ từ volatile cho phép nội dung của một biến được đồng bộ hóa trên tất cả các luồng đang chạy. Một luồng là một đường dẫn thực thi độc lập của mã trong một chương trình. Nhiều luồng có thể chạy đồng thời trong một chương trình. Bổ từ volatile chỉ được áp dụng cho các trường. Các constructor, phương thức, lớp và interface không thể sử dụng bổ từ. Bổ từ volatile không được sử dụng thường xuyên.
Trong quá trình làm việc với một chương trình đa luồng (multi thread), từ khóa volatile được sử dụng. Khi nhiều luồng của một chương trình sử dụng cùng một biến, nói chung, mỗi luồng có bản sao riêng của biến đó trong bộ nhớ cache cục bộ. Trong trường hợp như vậy, nếu giá trị của biến được cập nhật, nó sẽ cập nhật bản sao trong bộ nhớ cache cục bộ và không phải biến chính được lưu trong bộ nhớ. Luồng khác sử dụng cùng một biến không nhận được giá trị đã được cập nhật.
Để tránh vấn đề này, một biến được khai báo là volatile để chỉ ra rằng nó sẽ không được lưu trong bộ nhớ cache cục bộ.
Ngoài ra, khi một luồng cập nhật giá trị của biến, nó cập nhật biến hiện diện trong bộ nhớ chính. Điều này giúp các luồng khác có thể truy cập giá trị đã cập nhật.
Ví dụ:
private volatile int testvalue; // volatile variable
Bổ từ native
Trong một số trường hợp, chúng ta cần sử dụng một phương thức trong chương trình Java mà nằm ngoài JVM. Để mục đích này, Java cung cấp bổ từ native. Bổ từ native chỉ được sử dụng với các phương thức. Nó cho biết rằng triển khai của phương thức nằm ở một ngôn ngữ khác ngoài Java, chẳng hạn như C hoặc C++. Các constructor, trường, lớp và giao diện không thể sử dụng bổ từ này. Các phương thức được khai báo bằng bổ từ native được gọi là phương thức native.
Tệp nguồn Java thường chỉ chứa khai báo của phương thức native và không chứa triển khai của nó. Trong trường hợp của bổ từ native, triển khai của phương thức tồn tại trong một thư viện nằm ngoài JVM. Trước khi gọi một phương thức native, thư viện chứa triển khai phương thức phải được tải bằng cách thực hiện lệnh gọi từ hệ thống như sau:
System.loadLibrary("LibraryName");
Để khai báo một phương thức native, phương thức phải được đánh dấu bằng bổ từ native. Ngoài ra, không cung cấp triển khai cho phương thức đó. Ví dụ:
public native void nativeMethod();
Ví dụ minh họa việc tải một thư viện có tên NativeMethodDefinition chứa một phương thức native có tên nativeMethod():
public class NativeModifier {
native void nativeMethod(); // khai báo một phương thức native
// khối mã tĩnh để tải thư viện
static {
System.loadLibrary("NativeMethodDefinition");
}
public static void main(String[] args) {
NativeModifier objNative = new NativeModifier(); // Dòng 1
objNative.nativeMethod(); // Dòng 2
}
}
Lưu ý rằng một khối mã tĩnh được sử dụng để tải thư viện. Ở đây, từ khóa static cho biết rằng thư viện được tải ngay khi lớp được tải. Điều này đảm bảo rằng thư viện sẵn có khi cuộc gọi đến phương thức native được thực hiện. Phương thức native có thể được sử dụng giống như một phương thức không phải là native. Ví dụ, để gọi phương thức nativeMethod(), bạn có thể viết mã như được hiển thị ở dòng 1 và dòng 2 của đoạn mã trên.
Phương thức native cho phép truy cập vào các quy trình thư viện hiện có được tạo bên ngoài JVM. Tuy nhiên, việc sử dụng phương thức native mang đến hai vấn đề quan trọng. Chúng là:
- Tiềm ẩn Rủi ro bảo mật: Phương thức native thực thi mã máy thực tế và do đó, nó có thể truy cập vào bất kỳ phần nào của hệ thống máy chủ. Điều này có nghĩa là mã native không bị giới hạn trong môi trường thực thi của JVM. Điều này có thể dẫn đến việc nhiễm virus trên hệ thống đích.
- Mất tính di động: Mã native được gói trong một tệp DLL, để có thể tải trên máy tính mà chương trình Java đang chạy. Mỗi phương thức native phụ thuộc vào CPU và hệ điều hành. Điều này làm cho DLL không thể chuyển sang các máy khác. Điều này có nghĩa là một ứng dụng Java sử dụng phương thức native sẽ chỉ chạy trên các máy tính đã cài đặt DLL tương thích.
Bổ từ transient
Khi một ứng dụng Java được thực thi, các đối tượng được tải vào Bộ Nhớ Truy Cập Ngẫu Nhiên (RAM). Tuy nhiên, các đối tượng cũng có thể được lưu trữ trong bộ nhớ lưu trữ lâu dài ngoài JVM để có thể sử dụng sau này. Điều này xác định phạm vi và tuổi thọ của một đối tượng. Quá trình lưu trữ một đối tượng trong bộ nhớ lưu trữ lâu dài được gọi là tuần tự hóa (serialization). Đối với bất kỳ đối tượng nào cần được tuần tự hóa, lớp phải triển khai interface Serializable.
Tuy nhiên, nếu từ khóa transient được sử dụng với một biến, nó sẽ không được lưu trữ và sẽ không trở thành một phần của trạng thái lưu trữ lâu dài của đối tượng. Từ khóa transient hữu ích để ngăn dữ liệu nhạy cảm về bảo mật khỏi việc sao chép sang nguồn mà không có cơ chế bảo mật nào đã được triển khai. Ngoài ra, từ khóa transient giúp giảm lượng dữ liệu được tuần tự hóa, cải thiện hiệu suất và giảm chi phí.
Từ khóa transient chỉ có thể được sử dụng với biến thể hiện. Nó thông báo cho JVM không lưu trữ biến khi đối tượng, mà nó được khai báo trong, được tuần tự hóa. Do đó, khi đối tượng được lưu trữ trong bộ nhớ lưu trữ lâu dài, biến thể được khai báo là transient không được lưu trữ. Đoạn mã dưới mô tả việc tạo một biến transient.
public class Circle {
transient float PI; // biến transient sẽ không được lưu trữ
float area; // biến sẽ được lưu trữ
}
Bổ từ final
Từ khóa final được sử dụng khi muốn hạn chế việc sửa đổi một lớp hoặc thành viên dữ liệu. Từ khóa final có thể được sử dụng với biến, phương thức và lớp.
Một biến được khai báo là final là một hằng số, giá trị của nó không thể được thay đổi sau khi khai báo. Một biến final được gán một giá trị tại thời điểm khai báo. Một lỗi tại thời gian biên dịch sẽ xảy ra nếu một biến final được gán lại giá trị trong chương trình sau khi nó đã được khai báo. Đoạn mã dưới thể hiện việc tạo một biến final:
final float PI = 3.14;
Biến PI được khai báo là final để giá trị của nó không thể thay đổi sau này.
Một phương thức khai báo là final không thể được ghi đè hoặc ẩn trong một lớp con Java. Lý do sử dụng một phương thức final là để ngăn các lớp con thay đổi ý nghĩa của phương thức và tăng hiệu suất mã thông qua việc cho phép trình biên dịch biến các cuộc gọi phương thức thành mã Java nội tuyến (inline). Một phương thức final thường được sử dụng để tạo một hằng số ngẫu nhiên trong ứng dụng toán học. Đoạn mã dưới thể hiện việc tạo một phương thức final:
final float getCommission(float sales) {
System.out.println("A final method");
}
Phương thức getCommission() có thể được sử dụng để tính toán hoa hồng dựa trên doanh số hàng tháng. Cài đặt của phương thức này không thể bị sửa đổi bởi các lớp khác vì nó được khai báo là final. Một phương thức final không thể được khai báo là abstract(trừu tượng) vì nó không thể bị ghi đè.
Lưu ý – từ khóa abstract được sử dụng với phương thức và lớp. Một phương thức trừu tượng (abstract) không có thân và một lớp trừu tượng không thể được khởi tạo và phải tạo lớp con để kế thừa.
Một lớp được khai báo là final không thể được kế thừa hoặc làm lớp con. Một lớp như vậy trở thành một lớp tiêu chuẩn và phải được sử dụng như nó. Các biến và phương thức của một lớp được khai báo là final cũng được hiểu ngầm là final và không thể bị sửa đổi bởi các lớp khác. Đoạn mã dưới thể hiện việc tạo một lớp final.
Ví dụ triển khai một lớp final:
public final class Stock {
}
Lớp Stock được khai báo là final. Tất cả các thành viên dữ liệu bên trong lớp này cũng được ngầm hiểu là final và không thể bị sửa đổi bởi các lớp khác.
public class FinalDemo {
// Declare and initialize a final variable
final float PI;
// Constructor to initialize PI
public FinalDemo(float pi) {
PI = pi; // Initialize PI with the provided value
}
// Display the value of PI
public void display() {
System.out.println("The value of PI is: " + PI);
}
public static void main(String[] args) {
// Instantiate the FinalDemo class
FinalDemo objFinalDemo = new FinalDemo(22.75);
// Invoke the display() method
objFinalDemo.display();
}
}
Lớp FinalDemo bao gồm một biến final kiểu float có tên PI với giá trị là 3.14. Phương thức display() được sử dụng để gán một giá trị mới được truyền bởi người dùng vào biến PI. Tuy nhiên, việc này sẽ dẫn đến lỗi biên dịch ‘không thể gán giá trị cho biến final PI’. Nếu người dùng vẫn cố gắng chạy chương trình, một lỗi runtime sẽ được trả ra như đã thể hiện trong thông báo.
Vì biến final đã được gán giá trị cố định và không thể thay đổi sau khi đã được gán, việc cố gắng gán giá trị mới cho nó là không hợp lệ và sẽ dẫn đến lỗi biên dịch.
Để loại bỏ lỗi, tham số của hàm khởi tạo cần được thay đổi và câu lệnh “PI = pi;” nên được loại bỏ.
Quy tắc khi sử dụng các bổ từ (modifier) cho trường (field)
- Trường final không thể là volatile.
- Các phương thức native trong Java không thể có phần thân (body).
- Tránh khai báo một trường transient như là static hoặc final nếu có thể.
- Các phương thức native vi phạm tính năng độc lập của nền tảng Java, do đó không nên sử dụng thường xuyên.
- Một biến transient không được phép khai báo là final hoặc static.
Biến lớp (Class Variable)
Xét tình huống trong đó người dùng muốn tạo một bộ đếm để theo dõi số lượng đối tượng truy cập một phương thức cụ thể. Nếu người dùng tạo một biến thể hiện có tên là counter, mỗi đối tượng sẽ có một bản sao riêng của biến counter. Do đó, người dùng không thể theo dõi số lần một phương thức được truy cập. Trong tình huống như vậy, cần một biến có thể được chia sẻ giữa tất cả các đối tượng của một lớp và bất kỳ thay đổi nào được thực hiện trên biến đó chỉ được cập nhật trong một bản sao chung. Java cung cấp triển khai cho khái niệm này thông qua biến lớp (class variables) bằng cách sử dụng từ khóa static.
Khai Báo Biến Lớp
Biến lớp cũng được gọi là biến tĩnh (static variables). Lưu ý rằng biến tĩnh không phải là hằng số. Các biến như vậy liên quan đến lớp chứ không phải đối tượng. Nói cách khác, tất cả các thể hiện của lớp chia sẻ cùng một giá trị của biến lớp. Giá trị của một biến tĩnh có thể được sửa đổi bằng cách sử dụng các phương thức lớp hoặc các phương thức thể hiện. Tuy nhiên, khác với biến thể hiện, chỉ tồn tại một bản sao của biến tĩnh cho tất cả các đối tượng ở một vị trí cố định trong bộ nhớ. Tuy nhiên, biến tĩnh khai báo là final sẽ trở thành một hằng số và không thể sửa đổi giá trị.
public class StaticVariableDemo {
// Static variable that can be modified
static int PI = -14;
// Static constant that cannot be modified
static final double PI_CONSTANT = 3.14;
public static void main(String[] args) {
// You can use both PI and PI_CONSTANT in this class
System.out.println("PI: " + PI);
System.out.println("PI_CONSTANT: " + PI_CONSTANT);
}
}
Tạo Biến Tĩnh (Static Variables), Phương Thức Tĩnh (Static Methods) và Khối Tĩnh (Static Blocks)
Người dùng cũng có thể tạo phương thức tĩnh và khối khởi tạo tĩnh cùng với biến tĩnh. Biến và phương thức tĩnh có thể được thao tác mà không cần tạo một thể hiện của lớp (đối tượng). Điều này có thể xảy ra vì chỉ có một bản sao của một dữ liệu thành viên tĩnh được chia sẻ bởi tất cả các đối tượng. Một phương thức tĩnh chỉ có thể truy cập các biến tĩnh, không thể truy cập các biến thể hiện.
Các phương thức được khai báo là static có các ràng buộc sau:
- Chỉ có thể gọi các phương thức tĩnh.
- Chỉ có thể truy cập dữ liệu tĩnh.
- Không thể sử dụng từ khóa this hoặc super.
Một khối khởi tạo tĩnh được sử dụng để khởi tạo các biến tĩnh ngay khi lớp được khởi động. Chúng được sử dụng khi một khối mã phải được thực thi trong quá trình tải lớp bởi JVM. Nó được bao bọc trong cặp ngoặc nhọn “{}”.
Thông thường, một constructor được sử dụng để khởi tạo các biến. Đây là cách tiếp cận tốt nhất vì constructor được gọi ngầm khi một đối tượng được tạo. Tuy nhiên, đôi khi một lập trình viên cần phải tạo đối tượng trước khi có thể thực hiện bất kỳ thứ gì trong chương trình. Điều này xảy ra vì cần một đối tượng để gọi một biến thể hiện hoặc phương thức. Tuy nhiên, thay vì sử dụng constructor, một khối khởi tạo tĩnh có thể được sử dụng để khởi tạo các biến tĩnh, vì khối khởi tạo tĩnh được thực thi ngay trước khi phương thức main() được thực thi. Điều này có nghĩa là việc thực thi mã Java bắt đầu từ các khối khởi tạo tĩnh và không phải từ phương thức main(). Có thể có nhiều khối khởi tạo tĩnh trong một chương trình và chúng có thể được đặt bất kỳ đâu trong lớp. Một khối khởi tạo tĩnh chỉ có thể tham chiếu đến các biến lớp đã được khai báo trước nó.
Đoạn mã sau thể hiện một ví dụ về biến tĩnh, phương thức tĩnh và khối khởi tạo tĩnh.
public class StaticDemo {
// Static variable
static int count = 0;
// Static block
static {
System.out.println("Static block is executed.");
count = 10;
}
// Static method
static void display() {
System.out.println("Static method is called.");
System.out.println("Count: " + count);
}
public static void main(String[] args) {
// Calling static method
display();
}
}
Ví dụ 2
public class StaticMembers {
// Static block
static {
System.out.println("Static block");
}
// Static method
public static void staticMethod() {
System.out.println("Static method");
}
// Static counter
static int staticCounter = 0;
// Instance counter
int instanceCounter = 0;
// Display the value of static and instance counters
public void displayCount() {
// Increment the static and instance variable
staticCounter++;
instanceCounter++;
// Print the value of static and instance variables
System.out.println("Static counter: " + staticCounter);
System.out.println("Instance counter: " + instanceCounter);
}
public static void main(String[] args) {
System.out.println("Inside the main method");
// Invoke the staticMethod using the class name
StaticMembers.staticMethod();
// Create the first instance of the class
StaticMembers objStatic1 = new StaticMembers();
objStatic1.displayCount();
// Create the second instance of the class
StaticMembers objStatic2 = new StaticMembers();
objStatic2.displayCount();
// Create the third instance of the class
StaticMembers objStatic3 = new StaticMembers();
objStatic3.displayCount();
}
}
Bài tập
Bài 1:
**Đề bài: Chương Trình Quản Lý Nhân Sự**
**Mục tiêu**: Xây dựng một chương trình quản lý nhân sự đơn giản bằng ngôn ngữ Java để quản lý thông tin của các nhân sự trong công ty.
**Yêu cầu:**
Tạo một package hvc.emp
1. Tạo một lớp `Employee` trong package với các thuộc tính sau:
- `name` (tên) - kiểu dữ liệu String.
- `country` (quê quán) - kiểu dữ liệu String.
- `birthYear` (năm sinh) - kiểu dữ liệu int.
- `salary` (lương) - kiểu dữ liệu int.
Tạo 2 loại constructor cho lớp 0 tham số và đầy đủ tham số.
Tạo package hvc.main.
Yêu cầu: tất cả phương thức, thuộc tính lớp main sử dụng static
Tạo 1 class Main, bên trong hvc.main và chạy phương thức main() để làm điểm vào chương trình
2. Xây dựng chương trình với menu chức năng sau:
```
Chương Trình Quản Lý Nhân Sự
1. Nhập thông tin nhân sự
2. Sắp xếp nhân sự theo lương giảm dần
3. Phân tích theo quê quán
4. Tìm nhân sự theo quê quán và lương thấp nhất
5. Lưu danh sách nhân sự vào file
6. Mở file và hiển thị danh sách
7. Thoát
```
4. Nếu người dùng chọn một tùy chọn không hợp lệ, yêu cầu họ nhập lại.
6. Tạo các chức năng sau:
- **Nhập thông tin nhân sự (Chức năng 1)**:
- Cho phép người dùng nhập thông tin cho từng nhân sự, bao gồm tên, quê quán, năm sinh và lương.
- Yêu cầu sử dụng lớp ArrayList để lưu danh sách nhân sự đã nhập và
- **Sắp xếp nhân sự theo lương giảm dần (Chức năng 2)**:
- Hiển thị danh sách nhân sự đã nhập theo thứ tự giảm dần của lương.
- **Phân tích theo quê quán (Chức năng 3)**:
- Đếm số lượng nhân sự theo quê quán và hiển thị kết quả.
- **Tìm nhân sự theo quê quán và lương thấp nhất (Chức năng 4)**:
- Yêu cầu người dùng nhập quê quán và mức lương thấp nhất (min).
- Hiển thị danh sách nhân sự có quê quán trùng khớp và lương cao hơn hoặc bằng mức lương thấp nhất.
- **Lưu danh sách nhân sự vào file (Chức năng 5)**:
- Yêu cầu người dùng nhập tên file cần xuất ra.
- Tạo file ở chế độ nhị phân.
- Nếu không tạo được file, báo lỗi cho người dùng.
- Nếu thành công, ghi dữ liệu danh sách nhân sự vào file và thông báo đã lưu thành công.
- **Mở file và hiển thị danh sách (Chức năng 6)**:
- Yêu cầu người dùng nhập tên file cần mở.
- Tạo file ở chế độ nhị phân.
- Nếu không mở được file, báo lỗi cho người dùng.
- Đọc dữ liệu từ file và hiển thị ra màn hình.
- **Thoát (Chức năng 7)**:
- Kết thúc chương trình.
Bài 2:
Tạo package hvc.product
Viết chương trinh mô phỏng một phần mềm quản lý sanr phẩm và bán hàng mxh theo yêu cầu sau:
1.Tạo lớp Product theo cấu trúc sau:
Id Integer
name String
price Float
quantity Integer
Tạo package hvc.order
2. Tạo lớp Order theo cấu trúc sau:
Id Integer
quantity Integer
Date LocalDateTime
Mô phỏng sản phẩm và số lượng
Tạo package hvc.main
Tạo class Main để quản lý chương trình với yc:
Tất cả thuộc tính, phương thức của lớp Main phải là concrete (không sử dụng static)
- Tạo header menu chương trình
Chuong trinh quan ly san pham |
1. Nhap lieu |2. Bao cao het hang |3. Sap xep theo gia|4. Banhang|5. Bao cao|6. Thoat |
Menu sẽ hiển thị khi chương trình bắt đầu
Nhập vào danh sách sản phẩm
Nếu người dùng chọn option 1, chương trình thực hiện những yêu cầu sau:
- Yêu cầu người dùng nhập tổng số sp cần nhập liệu
- Nhập thông tin của mỗi sp
- Validate thông tin nhập vào theo yêu cầu:
0<= giá (price) <= 10000000
0<= số lượng
Khi người dùng chọn option 2, hiển thị ra bảng danh sách sắp xếp từ nhỏ đến lớn 3 sản phẩm có số lượng thấp nhất
Khi người dùng chọn option 3, hiển thị ra danh sách sắp xếp giá giảm dần
Khi người dùng chọn option 4
Hiển thị bảng danh sách sản phẩm , yêu cầu người dùng nhập ID sản phẩm cần bán và quantity theo yêu cầu:
Id phải là Id của sản phẩm vừa nhập
quantity phải nhỏ hơn quantity sản phẩm vừa nhập
Sau khi nghiệp vụ bán hoàn tất, trừ số lượng của sản phẩm tương ứng
- Khi người dùng chọn option 5
Hiển thị lịch sử bán
- Khi người dùng chọn option 6, thoát chương trình