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.

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ạiPhâ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ậymain(static) phảinewđố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 29Ba 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,
finalkhóa biến, không khóa đối tượng —final Listvẫnaddđượ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 finaltạ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ì.
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 stateRanh 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
statickhôngfinal, hoặcstaticfield 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 soLuongvàoNhanVienvà tăng trong constructor — tạo ba đối tượng, inNhanVien.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ọiaddthà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!
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.


