Hằng số và final — giá trị khắc trên đá
final trên biến, parameter, method và class; static final cho hằng số UPPER_SNAKE_CASE; vì sao final reference không đồng nghĩa immutable object — và pattern immutable class hoàn chỉnh.
TL;DR: final là cách Java nói "không đổi được" — nhưng nó áp lên biến (không gán lại), method (không override) và class (không extend) với ý nghĩa khác nhau. static final + UPPER_SNAKE_CASE là convention cho hằng số cấp class; với primitive/String literal, nó còn là compile-time constant được compiler inline. Bẫy lớn nhất của bài: final reference chỉ cố định địa chỉ, không cố định nội dung object — final List vẫn add() được như thường. Muốn bất biến thật sự phải dùng immutable object (List.of, String) hoặc tự viết immutable class: final class + final fields + không setter.
Bài 1 đến 7 tập trung vào giá trị thay đổi được. Bài này đi vào chiều ngược lại: khi nào bạn muốn một giá trị không được phép thay đổi — và keyword final thể hiện điều đó ở ba cấp độ khác nhau: biến, method, class. Còn cơ chế thứ ba của Java cho "tập giá trị cố định" — enum — có riêng bài kế tiếp.
1. Analogy — Khắc trên đá
Hãy nghĩ về hai kiểu "ghi":
- Viết trên giấy (biến thường) — dễ tẩy xoá, gán lại tuỳ ý.
- Khắc trên đá (
final) — một khi đã khắc, không đổi được. Muốn "đổi" phải đục bỏ tảng đá cũ, lấy tảng mới.
| Đời thường | Concept |
|---|---|
| Viết trên giấy | Biến thường — gán lại tuỳ ý |
| Khắc trên đá | final — gán đúng 1 lần |
| Bảng giá niêm yết của cửa hàng | static final — hằng số dùng chung cấp class |
2. Ba cơ chế "không đổi được" trong Java
| Cơ chế | Áp dụng lên | Ý nghĩa | Convention đặt tên |
|---|---|---|---|
final biến | Local var, field, param | Không thể gán lại sau khi khởi tạo | camelCase thường |
static final | Field cấp class | Hằng số dùng chung, compile-time constant | UPPER_SNAKE_CASE |
enum | Kiểu dữ liệu mới | Tập hợp giá trị cố định, type-safe | UPPER_SNAKE_CASE cho values |
Hai cơ chế đầu là chủ đề của bài này; enum được mổ riêng ở bài 09.
flowchart TD fin["final keyword"] v["bien / field / param<br/>khong gan lai duoc"] m["method<br/>subclass khong override duoc"] c["class<br/>khong extend duoc"] note["final reference != immutable object"] fin --> v fin --> m fin --> c v --> note
3. final trên biến
3.1 Local variable
final int maxSize = 100;
maxSize = 200; // COMPILE ERROR: cannot assign a value to final variable
Lưu ý convention: final local variable vẫn đặt tên camelCase như biến thường — chỉ static final (hằng số cấp class) mới dùng UPPER_SNAKE_CASE.
final local variable phải được gán đúng 1 lần trước khi dùng. Không nhất thiết gán ngay lúc khai báo:
final int threshold;
if ("strict".equals(mode)) { // so sanh String bang equals, khong dung ==
threshold = 10;
} else {
threshold = 50;
}
// threshold da duoc gan 1 lan -- OK
System.out.println(threshold);
// threshold = 99; // COMPILE ERROR
3.2 Field
public class Circle {
final double radius; // phai gan trong constructor hoac ngay tai day
Circle(double r) {
this.radius = r; // gan 1 lan trong constructor
// this.radius = r * 2; // COMPILE ERROR: da gan roi
}
}
3.3 Parameter
void process(final String input) {
// input = "other"; // COMPILE ERROR
System.out.println(input.toUpperCase()); // doc thoai mai
}
Khai báo parameter final ngăn vô tình gán lại bên trong method — hữu ích để đọc code rõ intent.
4. static final — hằng số cấp class
public class Config {
public static final int MAX_RETRIES = 3;
public static final double TAX_RATE = 0.1;
public static final String DEFAULT_LOCALE = "vi-VN";
}
// Dung o noi khac:
int retry = Config.MAX_RETRIES; // khong can tao object Config
Quy tắc:
- Convention đặt tên:
UPPER_SNAKE_CASE public static finalvới primitive hoặcStringliteral → compile-time constant — compiler inline giá trị vào chỗ dùng, không cần truy cập field lúc runtime.
// ANTI-PATTERN -- khong lam the nay
interface AppConstants {
int MAX_RETRIES = 3; // ngam la public static final
String LOCALE = "vi-VN";
}
class MyService implements AppConstants { ... } // BAD!
"Constant interface pattern" bị xem là anti-pattern trong Java (Effective Java Item 22): interface nên mô tả hành vi, không nên là nơi chứa hằng số. Hằng số nên đặt trong utility class hoặc enum. Nếu class implements interface hằng số, các constant đó "lộ" ra API public của class — không cần thiết.
Thay vào đó:
// DUNG: utility class voi constructor private
public final class AppConstants {
private AppConstants() {} // ngan instantiate
public static final int MAX_RETRIES = 3;
public static final String LOCALE = "vi-VN";
}
5. final trên class và method
5.1 final class — không thể extends
public final class Ssn { // Social Security Number
private final String value;
Ssn(String value) { this.value = value; }
// khong ai co the extends Ssn va override logic validation
}
// class MaliciousSsn extends Ssn { ... } // COMPILE ERROR
Java standard library dùng nhiều final class: String, Integer, Double, LocalDate, UUID... Lợi ích:
- Security: ngăn ai ghi đè method để bypass logic quan trọng
- Performance: JIT compiler biết không có subclass → có thể inline method call
- Design: truyền đạt "class này là final, không được mở rộng"
5.2 final method — không thể override
public class Payment {
public final void deduct(double amount) {
// logic kieu toan quan trong, khong duoc phep override
validate(amount);
doDeduct(amount);
log(amount);
}
protected void doDeduct(double amount) { /* subclass co the customize */ }
}
final method cho phép subclass kế thừa nhưng không thể thay đổi method đó. Hữu ích trong template method pattern: skeleton của thuật toán là final, các bước cụ thể thì protected có thể override.
6. final reference KHÔNG phải immutable object
Đây là điểm quan trọng nhất của bài:
final List<String> list = new ArrayList<>();
// list = new ArrayList<>(); // COMPILE ERROR: reference khong doi duoc
list.add("hello"); // OK! -- reference co dinh, nhung object thi van sua duoc
list.add("world"); // OK!
list.clear(); // OK!
final chỉ cố định địa chỉ (reference), không cố định nội dung object mà reference trỏ tới.
final reference | Immutable object | |
|---|---|---|
| Reference thay đổi? | ❌ Không | Có thể thay đổi |
| Nội dung object thay đổi? | ✅ Vẫn được (nếu object mutable) | ❌ Không được |
| Ví dụ | final List<String> list | String, List.of(...), LocalDate |
// Immutable object thuc su: List.of(), String, LocalDate
final List<String> immutableList = List.of("a", "b", "c");
// immutableList.add("d"); // UnsupportedOperationException -- object immutable
// immutableList = new ArrayList<>(); // COMPILE ERROR -- final reference
// final + mutable object: reference co dinh nhung noi dung doi duoc
final StringBuilder sb = new StringBuilder("hi");
sb.append("!"); // OK -- object van sua duoc
// sb = new StringBuilder(); // COMPILE ERROR -- reference co dinh
final như khắc địa chỉ nhà lên đá: địa chỉ không đổi được, nhưng đồ trong nhà vẫn di chuyển được.
Immutable object như ngôi nhà xi măng đổ liền: nội dung không đổi, nhưng địa chỉ (reference) vẫn có thể trỏ sang.
7. Immutable class pattern
Kết hợp final class + final fields + không có setter → class hoàn toàn immutable:
public final class Money {
private final long amountCents; // tien tinh bang xu, tranh float
private final String currency; // "VND", "USD"
public Money(long amountCents, String currency) {
if (amountCents < 0) throw new IllegalArgumentException("amount < 0");
this.amountCents = amountCents;
this.currency = currency;
}
public long getAmountCents() { return amountCents; }
public String getCurrency() { return currency; }
// "Thay doi" tao object moi, khong sua object hien tai
public Money add(Money other) {
if (!this.currency.equals(other.currency)) throw new IllegalArgumentException("Currency mismatch");
return new Money(this.amountCents + other.amountCents, this.currency);
}
@Override
public String toString() {
return amountCents + " " + currency;
}
}
// Su dung
Money a = new Money(100_000, "VND");
Money b = new Money(50_000, "VND");
Money total = a.add(b); // tao Money moi 150_000 VND
System.out.println(total); // 150000 VND
System.out.println(a); // 100000 VND -- a khong doi
Immutable class có nhiều lợi ích: thread-safe tự nhiên, dễ reasoning, an toàn làm HashMap key.
8. Bảng tổng hợp: final trên từng target
| Target | Ý nghĩa | Ví dụ |
|---|---|---|
| Local variable | Không thể gán lại | final int x = 10; |
| Field | Gán 1 lần tại khai báo hoặc constructor | final double rate; |
| Parameter | Không thể gán lại trong method | void f(final String s) |
| Method | Subclass không thể override | public final void pay() |
| Class | Không thể extend (subclass) | public final class Token |
9. Pitfall tổng hợp
❌ final reference = immutable object: final List<String> list — list không thể trỏ sang list khác, nhưng list.add(...) vẫn OK.
❌ Constant Interface anti-pattern: interface chứa hằng số và class implements nó — hằng số lộ ra API public. Dùng utility class final class với constructor private thay thế.
❌ UPPER_SNAKE_CASE cho final local variable: convention này chỉ dành cho static final cấp class. final int MAX_SIZE = 100; trong body method gây hiểu nhầm là hằng số dùng chung — đặt final int maxSize như biến thường.
❌ Quên private AppConstants() {}: utility class hằng số nên có constructor private để tránh bị khởi tạo không cần thiết.
10. 📚 Deep Dive Oracle
Spec chính thức Java 21:
- JLS §4.12.4 — final Variables: quy tắc "blank final" (khai báo không gán ngay), compile-time constant definition.
- JLS §8.1.1.2 — final Classes:
finalclass không thể có subclass;abstract finallà compile error. - JLS §15.29 — Constant Expressions: biểu thức nào được tính là constant expression — điều kiện để một field thành compile-time constant.
Diễn giải đơn giản: JLS §4.12.4 phân biệt "final variable" (không gán lại) và "compile-time constant" (primitive/String literal + final + gán ngay bằng constant expression) — compile-time constant được compiler inline thẳng giá trị vào chỗ dùng. §8.1.1.2 cho phép thư viện "đóng" hierarchy: String final để đảm bảo immutability, security và để JIT tự tin inline.
11. Tóm tắt
finalbiến: không thể gán lại.finalfield gán 1 lần tại khai báo hoặc constructor. Localfinalvẫn đặt tên camelCase.finalmethod: không thể override trong subclass — khoá behavior quan trọng nhưng vẫn cho extend class.finalclass: không thể extend. Ví dụ:String,Integer,LocalDate.finalreference ≠ immutable object: reference cố định, nhưng nội dung object vẫn thay đổi nếu object mutable.static finalhằng số cấp class: conventionUPPER_SNAKE_CASE. Đặt trong utility class với constructor private, không dùng constant interface.- Primitive/String
static finalgán ngay bằng literal → compile-time constant, được compiler inline. - Immutable class pattern:
final class+finalfields + không setter — thread-safe tự nhiên, an toàn làm HashMap key.
12. Tự kiểm tra
Q1Vì sao final List<String> list = new ArrayList<>(); cho phép list.add("x") nhưng không cho phép list = new ArrayList<>()?▸
final List<String> list = new ArrayList<>(); cho phép list.add("x") nhưng không cho phép list = new ArrayList<>()?final áp dụng cho biến list (reference), không phải object mà reference trỏ tới. list.add("x") gọi method trên object (ArrayList instance) — không đổi reference → OK. list = new ArrayList<>() gán reference mới vào biến list → vi phạm final → compile error. Muốn object không thay đổi nội dung: dùng List.of(...) hoặc Collections.unmodifiableList(...).Q2String là final class — điều đó có nghĩa là gì? Tại sao String cần là final?▸
String là final class — điều đó có nghĩa là gì? Tại sao String cần là final?Không thể extends String. Lý do:
- Immutability đảm bảo: subclass có thể override method để trả giá trị mutable, phá hủy thread-safety.
- Security: String dùng làm class name, file path, URL, SQL — nếu subclass override
equals()/hashCode()trả khác nhau mỗi lần, kẻ tấn công có thể bypass check. - String interning:
"abc" == "abc"hoạt động nhờ string pool; subclass mutable sẽ phá cơ chế pool. - JIT optimization: JVM inline/memoize String ops vì biết chắc không có override.
Integer, Long, các wrapper cũng đều final cùng lý do.
Q3Viết utility class chứa hằng số MAX_PAGE_SIZE = 100 và DEFAULT_CURRENCY = "VND" theo cách đúng (không dùng constant interface).▸
MAX_PAGE_SIZE = 100 và DEFAULT_CURRENCY = "VND" theo cách đúng (không dùng constant interface).public final class AppConstants {
public static final int MAX_PAGE_SIZE = 100;
public static final String DEFAULT_CURRENCY = "VND";
private AppConstants() {
throw new AssertionError("No instances.");
}
}Ba điểm quan trọng:
final class— không cho subclass.privateconstructor — không cho instantiate. Throw trong constructor chặn reflection abuse.public static final— hằng số truy cập quaAppConstants.MAX_PAGE_SIZE.
Constant interface bị coi là anti-pattern vì hằng số lộ ra API public của class implements — ràng buộc implementation detail vào type hierarchy.
Q4public static final int MAX_RETRIES = 3; là compile-time constant, còn public static final List<String> LOCALES = List.of("vi", "en"); thì không. Khác biệt nằm ở đâu và hệ quả thực tế là gì?▸
public static final int MAX_RETRIES = 3; là compile-time constant, còn public static final List<String> LOCALES = List.of("vi", "en"); thì không. Khác biệt nằm ở đâu và hệ quả thực tế là gì?final và khởi tạo ngay bằng constant expression (§15.29). MAX_RETRIES thoả → compiler inline giá trị 3 thẳng vào mọi chỗ dùng — runtime không còn field lookup. List.of(...) là method call, không phải constant expression → LOCALES chỉ là final field bình thường, đọc qua reference lúc runtime. Hệ quả thực tế: nếu thư viện đổi giá trị compile-time constant mà code dùng nó không được recompile, code đó vẫn giữ giá trị cũ đã inline — một điểm cần cân nhắc khi public constant trong API.Q5final method và final class khác nhau thế nào?▸
final method và final class khác nhau thế nào?finalmethod: method không thể bị@Overridetrong subclass. Class vẫn có thể đượcextends. Dùng khi muốn khóa một behavior critical (security check, invariant) nhưng vẫn cho phép mở rộng class.finalclass: class không thể đượcextends. Tất cả method trong đó ngầm hiểu là không thể override (vì không có subclass). Dùng khi muốn đóng hoàn toàn hierarchy (String,Integer, immutable value object).
final class mạnh hơn final tất cả method — không cho subclass thêm method mới cũng không cho thay field.
Bài tiếp theo: Enum — tập giá trị cố định, type-safe thay cho magic number
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên