Java Foundations
17/39
Bài 17 / 39~13 phútCú pháp Java & Kiểu dữ liệuLộ trình · chặng 02/21Miễn phí… lượt xem

Hằng số và final — giá trị khắc trên đá

Hiểu rõ từ khóa final trên biến, method và class trong Java: hằng số static final, compile-time constant và phân biệt final reference với immutable object.

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 đá mô tả từ khóa final như thế nào?

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ườngConcept
Viết trên giấyBiế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àngstatic final — hằng số dùng chung cấp class

2. Ba cơ chế không đổi được trong Java gồm những gì?

Cơ chếÁp dụng lênÝ nghĩaConvention đặt tên
final biếnLocal var, field, paramKhông thể gán lại sau khi khởi tạocamelCase thường
static finalField cấp classHằng số dùng chung, compile-time constantUPPER_SNAKE_CASE
enumKiểu dữ liệu mớiTập hợp giá trị cố định, type-safeUPPER_SNAKE_CASE cho values

Hai cơ chế đầu là chủ đề của bài này; enum được mổ riêng ở bài 09.

Keyword final toả ra ba nhánh: trên method là không override được, trên biến hoặc field hoặc tham số là không gán lại được, trên class là không extends được; riêng nhánh biến dẫn tiếp tới hệ quả final chỉ khoá địa chỉ chứ không khoá nội dung object

3. Từ khóa final trên biến hoạt động ra sao?

3.1 Local variable

Java
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:

Java
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

Java
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

Java
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.

Thử ngẫmmột constructor trong dự án bạn gán field qua vài method phụ gọi bên trong nó, không gán trực tiếp — theo quy tắc "gán đúng 1 lần" của final, cách viết đó có compile được không?

4. Hằng số static final cấp class được định nghĩa và tối ưu như thế nào?

Java
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 final với primitive hoặc String literal → 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: Constant Interface
Java
// 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 đó:

Java
// 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";
}

Thử ngẫmbạn thấy một interface trong dự án chỉ chứa toàn hằng số, không method nào — bạn để nguyên vì "chạy tốt mà", hay đề nghị refactor sang utility class? Vì sao?

5. Từ khóa final trên class và method có tác dụng gì?

5.1 final class — không thể extends

Java
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

Java
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. Tại sao final reference không đồng nghĩa với immutable object?

Đây là điểm quan trọng nhất của bài:

Java
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 referenceImmutable object
Reference thay đổi?❌ KhôngCó thể thay đổi
Nội dung object thay đổi?✅ Vẫn được (nếu object mutable)❌ Không được
Ví dụfinal List<String> listString, List.of(...), LocalDate
Java
// 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
💡 Cách nhớ — final reference vs immutable

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.

Thử ngẫmmột field final List<String> tags trong entity của bạn — đồng nghiệp giả định nó bất biến và truyền thẳng ra ngoài class, nhưng thực ra có bất biến không? Rủi ro cụ thể là gì?

7. Mẫu hình thiết kế Immutable Class hoàn chỉnh như thế nào?

Kết hợp final class + final fields + không có setter → class hoàn toàn immutable:

Java
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 tác dụng của final trên từng vị trí khai báo là gì?

TargetÝ nghĩaVí dụ
Local variableKhông thể gán lạifinal int x = 10;
FieldGán 1 lần tại khai báo hoặc constructorfinal double rate;
ParameterKhông thể gán lại trong methodvoid f(final String s)
MethodSubclass không thể overridepublic final void pay()
ClassKhông thể extend (subclass)public final class Token

9. Các bẫy pitfall thường gặp với từ khóa final là gì?

❌ 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. Tài liệu Deep Dive Oracle quy định gì về từ khóa final?

📚 Deep Dive Oracle (optional)

Spec chính thức Java 21:

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. Điểm cốt lõi cần nhớ về final và hằng số là gì?

  • final trên biến: Chặn việc gán lại giá trị; với reference, chỉ cố định con trỏ chứ không cấm sửa đổi trạng thái đối tượng.
  • static final hằng số: Dùng chuẩn UPPER_SNAKE_CASE, đặt trong utility class có constructor private thay vì Constant Interface.
  • final trên method & class: Method cấm override để khóa khung thuật toán, class cấm extends để bảo vệ tính bất biến và an toàn.
  • Compile-time constant: Các hằng số nguyên thủy hoặc chuỗi literal được compiler inline trực tiếp tại nơi gọi.

12. Tự kiểm tra kiến thức về hằng số và final như thế nào?

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Vì sao final List<String> list = new ArrayList<>(); cho phép list.add("x") nhưng không cho phép list = new ArrayList<>()?
  2. Q2
    String là final class — điều đó có nghĩa là gì? Tại sao String cần là final?
  3. Q3
    Viế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).
  4. Q4
    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ì?
  5. Q5
    final method và final class khác nhau thế nào?

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?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Lộ trình Junior Java Backend Developer

Chặng 2/21 · Lập trình từ số 0 với Java — bài 17/32 của chặng

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Enum — tập giá trị cố định, type-safe thay cho magic number