Backend

99 Ngày Java — Ngày 18: static & final

SSite Admin
14 tháng 08, 2026 6 phút đọc 2 lượt xem
99 Ngày Java — Ngày 18: static & final

Đến hôm nay, mọi field và method ta viết đều thuộc về từng đối tượng: mỗi NhanVien giữ một bản ten, một bản tinhLuong() riêng. Nhưng có những dữ liệu thuộc về chính lớp — tổng số nhân viên đã tạo, mức lương tối thiểu theo quy định — tồn tại đúng một bản dù có bao nhiêu đối tượng. Java biểu diễn điều đó bằng static; và khi một giá trị, một method hay cả một lớp cần được khóa lại không cho thay đổi, công cụ là final. Hai từ khóa nhỏ, ảnh hưởng thiết kế lớn.

Sketchnote Ngày 18: static & final trong Java — thành viên của lớp, hằng số, static block, và khi nào nên tránh static

static — thành viên của lớp, không của đối tượng

public class NhanVien {
    private String ten;                    // instance field: MỖI đối tượng một bản
    private static int soLuong = 0;        // static field: MỘT bản cho CẢ LỚP

    public NhanVien(String ten) {
        this.ten = ten;
        soLuong++;                         // mọi đối tượng cùng nhìn thấy thay đổi
    }

    public static int getSoLuong() {       // static method: gọi qua TÊN LỚP,
        return soLuong;                    // không cần bất kỳ đối tượng nào
    }
}

new NhanVien("An");
new NhanVien("Bình");
System.out.println(NhanVien.getSoLuong());   // 2 — thuộc về lớp, không về đối tượng

// Quen thuộc từ Ngày 02: main là static —
// JVM gọi nó TRƯỚC KHI có bất kỳ đối tượng nào tồn tại
  • Phân định rạch ròi: instance member — mỗi đối tượng một bản, truy cập qua tham chiếu; static member — một bản duy nhất gắn với lớp, truy cập qua tên lớp.

  • Static method không có this: nó không đứng trên đối tượng nào nên không đọc được instance field — vì vậy main (static) phải new đối tượng trước khi dùng các method thường (câu hỏi kinh điển từ Ngày 02).

  • Các lớp utility trong JDK áp dụng đúng mô hình này: Math, Collections, Arrays — toàn static method, không trạng thái, thậm chí chặn khởi tạo bằng constructor private.

Static block & static import

public class CauHinh {
    static final Map<String, String> MA_VUNG;   // hằng số phức tạp, cần vài bước dựng

    static {                                    // static block: chạy đúng MỘT lần
        MA_VUNG = new HashMap<>();              // khi lớp được nạp (chi tiết Ngày 73)
        MA_VUNG.put("HN", "024");
        MA_VUNG.put("HCM", "028");
    }
}

// static import — dùng có chừng mực cho hằng số/method quá quen thuộc:
import static java.lang.Math.PI;
import static java.lang.Math.pow;

double dienTich = PI * pow(banKinh, 2);         // gọn hơn Math.PI * Math.pow(...)

// Lạm dụng static import khiến người đọc mất dấu nguồn gốc của method —
// giới hạn ở những tên không thể nhầm lẫn (PI, max, min...)
  • Static block chạy một lần khi lớp được nạp vào JVM — dành cho hằng số phức tạp cần nhiều bước khởi tạo; thứ tự nạp lớp và ClassLoader được phân tích kỹ ở Ngày 73.

  • Static import đưa hằng số/method static vào phạm vi tên trực tiếp — tăng độ gọn nhưng giảm khả năng truy nguyên; quy tắc thực dụng: chỉ dùng cho những tên không thể nhầm lẫn.

final — khóa biến, method và lớp

final double THUE_VAT = 0.08;              // biến final: gán đúng MỘT lần
// THUE_VAT = 0.10;                          // ✗ lỗi biên dịch

public static final int LUONG_TOI_THIEU = 4_680_000;
// static + final = hằng số của lớp — quy ước đặt tên UPPER_SNAKE_CASE

public final String xuatBaoCao() { ... }    // final method: cấm override —
                                            // đã gặp ở template method (Ngày 16)

public final class String { ... }           // final class: cấm kế thừa —
                                            // String trong JDK chính là final

final List<String> tags = new ArrayList<>();
tags.add("java");                           // ✓ final khóa BIẾN THAM CHIẾU...
// tags = new ArrayList<>();                // ✗ ...không khóa NỘI DUNG đối tượng
// Bất biến thật sự cần thiết kế riêng — hẹn Ngày 29
  • Ba cấp độ của final: biến (gán một lần), method (cấm override — bảo vệ trình tự như template method Ngày 16), lớp (cấm kế thừa — như String, đóng băng hành vi để bảo đảm an toàn).

  • Điểm tinh tế quan trọng nhất: với kiểu tham chiếu, final khóa biến, không khóa đối tượngfinal List vẫn add được. Bất biến thật sự là chủ đề thiết kế riêng của Ngày 29.

  • Kết hợp static final tạo ra hằng số của lớp — quy ước UPPER_SNAKE_CASE giúp người đọc nhận diện tức thì.

Dãy tủ máy chủ trong trung tâm dữ liệu — static member là hạ tầng dùng chung: một bản duy nhất phục vụ mọi đối tượng

Khi nào nên tránh static

// Static state có thể ghi (mutable) — nguồn lỗi khó truy vết:
public class GioHang {
    public static List<String> sanPham = new ArrayList<>();  // dùng chung TOÀN ứng dụng!
}

// Ba hệ quả thường gặp:
// 1. Hai luồng ghi đè dữ liệu của nhau (race condition — Ngày 62)
// 2. Test chạy lẻ thì đạt, chạy cả bộ thì hỏng — trạng thái dính từ test trước
// 3. Phụ thuộc bị che giấu: chữ ký method không hề cho thấy nó đọc/ghi GioHang

// Những chỗ static là lựa chọn ĐÚNG:
//   ✓ Hằng số:             public static final
//   ✓ Utility thuần túy:   Math.max, Collections.sort — không giữ trạng thái
//   ✓ Factory method:      List.of(...), CoTheThanhToan.macDinh() (Ngày 17)

// Bên series Spring: bean singleton (Spring Ngày 07) do container quản lý,
// inject tường minh qua constructor — thay thế lành mạnh cho static state
  • Ranh giới cần nhớ: static + bất biến (hằng số, utility thuần túy) là an toàn; static + có thể ghi là trạng thái toàn cục — kẻ thù của test độc lập và xử lý đa luồng.

  • Dấu hiệu nhận biết khi review: field static không final, hoặc static field kiểu collection có thể ghi — hai mẫu đáng dừng lại chất vấn trước khi merge.

  • Nhu cầu "một bản dùng chung" chính đáng đã có lời giải tốt hơn: bean singleton của Spring (Ngày 07) — vòng đời do container quản lý, phụ thuộc tường minh, thay thế được trong test.

Bài tập nhỏ

  • Thêm static int soLuong vào NhanVien và tăng trong constructor — tạo ba đối tượng, in NhanVien.getSoLuong() và giải thích vì sao không gọi qua từng đối tượng.

  • Trong static method, thử đọc một instance field — ghi lại thông báo lỗi biên dịch và giải thích bằng khái niệm this.

  • Khai báo final List<String>, gọi add thành công rồi thử gán lại biến — xác nhận ranh giới giữa khóa biến và khóa nội dung.

  • Tìm một lớp utility trong JDK (Math, Collections) trên tài liệu chính thức — kiểm chứng: toàn static method và không có constructor public.

Kết luận

static đưa thành viên lên cấp lớp — đúng chỗ cho hằng số, utility thuần túy và factory method, nhưng trở thành trạng thái toàn cục nguy hiểm khi kết hợp với dữ liệu có thể ghi. final khóa biến, method và lớp — công cụ thực thi các quyết định thiết kế đã gặp ở template method. Hai từ khóa này xuất hiện cùng nhau trong mẫu quan trọng nhất: public static final — hằng số của lớp. Ngày 19 ta khám phá các lớp được khai báo bên trong lớp khác: inner class, local class và anonymous class — người quen từ bài interface sẽ xuất hiện trở lại. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 18: Phân trang & sắp xếp API

Ngày 18 của 99 Ngày Spring: vì sao không trả cả bảng trong một response, Pageable tự bind page/size/sort từ query param, bọc Page thành PageResponse DTO để giữ hợp đồng ổn định, convention sắp xếp nhiều tiêu chí, và hai chốt an toàn — giới hạn max-page-size, whitelist field được sort.

14 thg 8, 20265 phút2
99 Ngày Spring — Ngày 17: Mapping DTO với MapStruct

Ngày 17 của 99 Ngày Spring: vì sao không trả thẳng entity, chi phí ẩn của map thủ công, MapStruct sinh code mapping lúc biên dịch từ interface khai báo, tùy biến với @Mapping và @MappingTarget, cùng unmappedTargetPolicy = ERROR biến field bỏ sót thành lỗi build.

13 thg 8, 20266 phút31
99 Ngày Java — Ngày 17: Interface

Ngày 17 của 99 Ngày Java: interface — hợp đồng thuần túy tách khỏi cây kế thừa, implement nhiều interface cùng lúc, default & static method cho phép API tiến hóa không phá vỡ mã cũ, và functional interface — cửa ngõ sang lambda và lập trình hàm.

13 thg 8, 20266 phút12