Từ Khóa: final (Final Keywords)
"Trong Java, từ khóa final là một công cụ mạnh mẽ để đảm bảo tính bất biến và an toàn trong lập trình. Nó được sử dụng để hạn chế việc thay đổi giá trị, ghi đè phương thức, hoặc kế thừa lớp, từ đó tăng tính ổn định và rõ ràng cho mã nguồn".
Từ khóa final có thể được áp dụng trong các ngữ cảnh sau:
↳ Biến final (hằng số)
↳ Phương thức final
↳ Lớp final
Ngoài ra, một biến final có thể là:
↳ Biến final rỗng: Là biến final chưa được khởi tạo ngay từ đầu. Loại biến này có thể được khởi tạo duy nhất một lần trong constructor.
↳ Biến final static: Được khởi tạo trong khối static duy nhất, đảm bảo tính bất biến toàn cục.
Sử dụng từ khóa final giúp chương trình trở nên rõ ràng và dễ bảo trì hơn. Chúng ta sẽ tìm hiểu chi tiết về từng ngữ cảnh sử dụng từ khóa final ở phần bên dưới.

Ⅰ. Biến final (Biến hằng)
↳ Khi khai báo một biến với từ khóa final, giá trị của biến đó không thể thay đổi sau khi được khởi tạo. Biến này được gọi là hằng số.
↳ Việc sử dụng biến final giúp tăng cường tính bảo mật cho chương trình, vì nó ngăn chặn việc vô tình thay đổi giá trị của các biến quan trọng.
↳ Biến final cũng có thể cải thiện hiệu suất chương trình, vì trình biên dịch Java có thể tối ưu hóa code tốt hơn cho các biến không thay đổi.
Cú pháp để khai báo biến final như sau:
Cú Pháp
final <kiểu_dữ_liệu> <tên_biến> = giá_trị_ban_đầu;
Dưới đây là giải thích chi tiết về cú pháp khai báo này:
↳ <kiểu_dữ_liệu>: là kiểu dữ liệu của biến, ví dụ như int
, double
, String
, ...
↳ <tên_biến>: là tên của biến.
↳ <giá_trị_ban_đầu>: là giá trị khởi tạo ban đầu của biến (không bắt buộc). Nếu có, bạn chỉ có thể gán giá trị này một lần và không thể thay đổi sau đó.
Ví dụ: Book.java
public class Book {
// Khai báo các trường final
final int MAX_SIZE = 100;// Độ dài tối đa
final double PI = 3.14159;// Giá trị PI
final String APP_NAME = "MyApp";// Tên ứng dụng
// Phương thức để thay đổi các giá trị final (lỗi sẽ xảy ra vì không thể gán lại giá trị cho final)
public void ChangeName(int MAX_SIZE, double PI, String APP_NAME) {
this.MAX_SIZE = MAX_SIZE; // Lỗi: Không thể gán lại giá trị cho một biến final
this.PI = PI;// Lỗi: Không thể gán lại giá trị cho một biến final
this.APP_NAME = APP_NAME;// Lỗi: Không thể gán lại giá trị cho một biến final
}
public static void main(String[] args) {
Book book = new Book();
book.setTitle(200, 3.14, "APP"); // Gọi phương thức setTitle với các giá trị mới
}
}
Kết quả của chương trình là:
Biến final trống
Biến final trống, hay còn gọi là biến final không khởi tạo (blank final variable), là một biến final mà không có giá trị ban đầu được gán cho nó khi khai báo. Trong Java, bạn có thể khai báo biến final trống và sau đó chỉ có thể gán giá trị cho nó trong constructor của lớp hoặc trong khối static (nếu là biến static).
Ví dụ: MyClass.java
class MyClass {
final int MAX_SIZE;
final double PI;
public MyClass(int MAX_SIZE, double PI) {
this.MAX_SIZE = MAX_SIZE;// Gán giá trị trong constructor
this.PI = PI;// Gán giá trị trong constructor
}
// static {
// PI = 3.14159;// Gán giá trị trong khối static
// }
public static void main(String[] args) {
MyClass obj = new MyClass(100, 3.14);
System.out.println("Kích thước tối đa: " + obj.MAX_SIZE);
System.out.println("PI= " + obj.PI);
}
}
Kết quả của chương trình là:
PI= 3.14
Lưu ý rằng sau khi một biến final trống được gán giá trị trong constructor hoặc khối static, bạn không thể thay đổi giá trị của nó.
Biến final static
↳ Biến final static trong Java là biến hằng được khai báo với từ khóa final và static.
↳ Biến final static là biến có giá trị không thay đổi và thuộc về lớp (class) chứ không phải đối tượng (instance) của lớp đó.
↳ Biến final static được khởi tạo trong khối static (static block) của lớp.
↳ Biến final static giúp bạn chia sẻ các hằng số được sử dụng nhiều lần trong toàn bộ chương trình.
Cú pháp để khai báo biến final static như sau:
Cú Pháp
final static <kiểu_dữ_liệu> <tên_biến> = <giá_trị_ban_đầu>;
final static <kiểu_dữ_liệu> <tên_biến>;
Ví dụ: MyClass.java
class MyClass {
final static int MAX_SIZE = 100;
final static double PI;
static {
PI = 3.14159;// Gán giá trị trong khối static
}
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println("Kích thước tối đa: " + obj.MAX_SIZE);
System.out.println("PI= " + MyClass.PI);
}
}
Kết quả của chương trình là:
PI= 3.14
Điều quan trọng cần lưu ý là:
↳ Hằng số MAX_SIZE và PI là final, do đó không thể thay đổi giá trị của chúng sau khi đã được gán giá trị ban đầu.
↳ MAX_SIZE được truy cập thông qua một đối tượng của lớp MyClass, trong khi PI có thể được truy cập trực tiếp thông qua lớp MyClass vì nó là biến static.
Khi bạn khai báo một tham số là final, bạn không thể thay đổi giá trị của nó sau khi đã được khởi tạo. Điều này áp dụng cho các biến cục bộ, tham số của phương thức và các thuộc tính của lớp. Việc sử dụng từ khóa final đảm bảo rằng giá trị của biến sẽ không bị thay đổi, tạo ra sự an toàn và nhất quán trong mã nguồn.
Ví dụ: Example.java
public class Example {
public void printMessage(final String message) {
message = "New Message"; // Lỗi: không thể gán giá trị mới cho biến final
System.out.println(message);
final int number = 10;
number = 20; // Lỗi: không thể gán giá trị mới cho biến final
System.out.println(number);
}
public static void main(String[] args) {
Example example = new Example();
example.printMessage("Hello, World!");
}
}
Kết quả của chương trình là:
Trong tất cả các ví dụ trên, biến final không thể thay đổi giá trị sau khi đã được khởi tạo. Điều này giúp ngăn chặn các lỗi không mong muốn và tạo ra mã nguồn dễ bảo trì hơn.
Ⅱ. Phương thức final
Khi một phương thức được khai báo là final, nó không thể bị ghi đè bởi bất kỳ phương thức nào trong lớp con. Điều này đảm bảo rằng hành vi của phương thức không thể bị thay đổi khi lớp được kế thừa. Trình biên dịch và máy ảo Java (JVM) có thể thực hiện tối ưu hóa hiệu suất cho các phương thức final vì biết rằng các phương thức này không thể bị ghi đè.
Cú pháp khai báo phương thức final:
Cú Pháp
final <kiểu_dữ_liệu_trả_về> <tên_phương_thức>(<danh_sách_tham_số>) {
// thân phương thức
}
Ví dụ: Example.java
class Parent {
final void show() {
System.out.println("Đây là phương thức final.");
}
}
class Child extends Parent {
//Không thể ghi đè phương thức show() của lớp Parent
@Override
void show() {
System.out.println("Đang cố gắng ghi đè");
}
}
public class Example {
public static void main(String[] args) {
Child obj = new Child();
obj.show(); // Gọi phương thức từ lớp Parent
}
}
Kết quả của chương trình là:
Việc sử dụng phương thức final thường được áp dụng khi bạn muốn đảm bảo rằng một phương thức cụ thể không bị thay đổi hành vi bởi các lớp con. Điều này có thể hữu ích trong các tình huống mà việc thay đổi hành vi của phương thức có thể gây ra lỗi hoặc ảnh hưởng đến tính toàn vẹn của lớp.
Kế thừa phương thức final
Từ khóa final có thể được kế thừa nhưng không thể bị ghi đè (override) bởi lớp con. Điều này có nghĩa là bạn có thể sử dụng phương thức final trong các lớp con, nhưng không thể thay đổi cách thức hoạt động của nó trong lớp con.
Ví dụ: Example.java
// Lớp cha (ParentClass)
class ParentClass {
// Phương thức final
public final void displayMessage() {
System.out.println("Đây là phương thức final từ ParentClass đuược gọi từ Lớp con (ChildClass).");
}
}
// Lớp con (ChildClass) kế thừa từ ParentClass
class ChildClass extends ParentClass {
// Cố gắng ghi đè phương thức final sẽ gây lỗi biên dịch
/*
@Override
public void displayMessage() {
System.out.println("Đang cố gắng ghi đè phương thức final trong ChildClass.");
}
*/
}
public class Example {
public static void main(String[] args) {
// tạo đối tượng từ lớp ChildClass
ChildClass child = new ChildClass();
child.displayMessage(); // Gọi phương thức final được kế thừa từ ParentClass
}
}
Kết quả của chương trình là:
Tóm lại, phương thức final có thể được kế thừa nhưng không thể bị ghi đè, đảm bảo rằng hành vi của phương thức không thể bị thay đổi bởi các lớp con, giúp bảo vệ tính toàn vẹn của phương thức và đảm bảo rằng các lớp con không thể làm sai lệch hành vi đã được xác định trong lớp cha. Điều này rất quan trọng khi bạn muốn bảo vệ một phần cụ thể của mã nguồn khỏi bị thay đổi không mong muốn trong quá trình kế thừa.
Ⅳ. Lớp final
Khi một lớp được khai báo với từ khóa final, nghĩa là lớp đó không thể bị kế thừa bởi bất kỳ lớp nào khác. Việc sử dụng lớp final thường nhằm mục đích bảo vệ cấu trúc và hành vi của lớp, đảm bảo rằng không ai có thể mở rộng và thay đổi chúng.
Cú pháp khai báo lớp final:
Cú Pháp
<kiểu_truy_cập> final class <tên_Lớp> {
// các thành phần của lớp
}
Ví dụ: Example.java
// Lớp Parent được khai báo là final, nghĩa là không thể kế thừa lớp này
public final class Parent {
// Phương thức show() của lớp Parent
void show() {
}
}
// Lớp Child cố gắng kế thừa từ lớp Parent, nhưng sẽ gây lỗi biên dịch vì lớp Parent là final
public class Child extends Parent {
// Phương thức show() của lớp Child cố gắng ghi đè phương thức show() của lớp Parent
void show() {
System.out.println("Đang cố gắng kế thừa");
}
}
// Lớp Example chứa phương thức main để chạy chương trình
public class Example {
public static void main(String[] args) {
// Tạo một đối tượng của lớp Child
Child obj = new Child();
// Gọi phương thức show() từ đối tượng obj
obj.show(); // Gọi phương thức từ lớp Parent
}
}
Kết quả của chương trình là:
Lớp final thường được sử dụng trong các tình huống sau:
↳ Lớp cung cấp các phương thức tiện ích, chẳng hạn như các lớp trong thư viện Java như java.lang.Math hoặc java.lang.String.
↳ Lớp đại diện cho các khái niệm mà việc kế thừa không có ý nghĩa, chẳng hạn như các lớp đại diện cho hằng số hoặc các dịch vụ không thay đổi.