Java static 與 final關(guān)鍵字實例詳解
深入理解 Java 中 static 和 final 關(guān)鍵字的本質(zhì),掌握類加載過程中的內(nèi)存變化,以及在多線程環(huán)境下的線程安全問題。
核心概念速覽
static:屬于類而非實例
本質(zhì):聲明為 static 的成員(變量、方法、代碼塊、內(nèi)部類)屬于類本身,而非任何對象實例。
內(nèi)存分配:在類加載時,static 成員分配在方法區(qū)(JDK 8+ 為元空間+堆的靜態(tài)存儲區(qū)),只有一份副本,所有實例共享。
final:不可變
本質(zhì):聲明為 final 的內(nèi)容一旦初始化就不能改變。
適用范圍:
- 類:不能被繼承(如
String) - 方法:不能被子類重寫
- 變量:引用不可變(基本類型值不變,對象引用地址不變)
static final:全局常量
本質(zhì):既是類級別的,又是不可變的。通常用于定義編譯期常量。
內(nèi)存優(yōu)化:編譯時常量會被編譯器內(nèi)聯(lián)到使用處,不會觸發(fā)類加載。
static 詳解
四種使用形式
1. 靜態(tài)變量(類變量)
public class Counter {
// 靜態(tài)變量 - 所有實例共享
private static int count = 0;
public Counter() {
count++; // 每創(chuàng)建一個實例,count 加 1
}
public static int getCount() {
return count;
}
}2. 靜態(tài)常量
public class MathConstants {
// 公開靜態(tài)常量
public static final double PI = 3.141592653589793;
public static final double E = 2.718281828459045;
// 私有靜態(tài)常量
private static final int MAX_PRECISION = 100;
}3. 靜態(tài)代碼塊
public class DatabaseConfig {
private static Properties config;
// 靜態(tài)代碼塊 - 類加載時執(zhí)行一次
static {
config = new Properties();
try (InputStream input = DatabaseConfig.class.getResourceAsStream("/config.properties")) {
config.load(input);
} catch (IOException e) {
throw new RuntimeException("加載配置文件失敗", e);
}
}
public static String get(String key) {
return config.getProperty(key);
}
}4. 靜態(tài)方法
public class StringUtils {
// 工具類方法,無需創(chuàng)建對象
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
public static String capitalize(String str) {
if (isEmpty(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}5. 靜態(tài)內(nèi)部類
public class Outer {
private static int outerStatic = 10;
private int outerInstance = 20;
// 靜態(tài)內(nèi)部類
public static class StaticInner {
private int innerVar = 30;
public void show() {
// 可以訪問外部類的靜態(tài)成員
System.out.println("outerStatic = " + outerStatic);
// 不能訪問外部類的實例成員
// System.out.println(outerInstance); // 編譯錯誤
}
}
public static void main(String[] args) {
Outer.StaticInner inner = new Outer.StaticInner();
inner.show();
}
}static 的內(nèi)存特點
public class StaticMemoryDemo {
static int a = 10; // 存儲在方法區(qū)
int b = 20; // 存儲在堆中
public static void main(String[] args) {
StaticMemoryDemo obj1 = new StaticMemoryDemo();
StaticMemoryDemo obj2 = new StaticMemoryDemo();
// 修改靜態(tài)變量
StaticMemoryDemo.a = 100;
obj1.b = 200;
obj2.b = 300;
System.out.println(obj1.a + ", " + obj1.b); // 100, 200
System.out.println(obj2.a + ", " + obj2.b); // 100, 300
// 修改實例變量不影響靜態(tài)變量
obj1.a = 1000; // 等價于 StaticMemoryDemo.a = 1000
System.out.println(StaticMemoryDemo.a); // 1000
}
}final 詳解
三種使用形式
1. final 類
// final 類不能被繼承
public final class String {
// String 類的實現(xiàn)
}
// 編譯錯誤
// public class MyString extends String {}2. final 方法
public class Parent {
// final 方法不能被子類重寫
public final void essentialMethod() {
System.out.println("重要方法,不允許修改行為");
}
public void normalMethod() {
System.out.println("普通方法,可以重寫");
}
}
public class Child extends Parent {
@Override
public void normalMethod() {
System.out.println("重寫普通方法");
}
// 編譯錯誤
// @Override
// public void essentialMethod() {}
}3. final 變量
public class FinalVariableDemo {
// final 實例變量 - 必須初始化
private final int x; // 方式1:構(gòu)造器初始化
private final int y = 20; // 方式2:聲明時初始化
// final 靜態(tài)變量
private static final int STATIC_FINAL = 30;
// final 實例變量 - 方式3:實例代碼塊初始化
private final int z;
{
z = 40;
}
public FinalVariableDemo(int x) {
this.x = x;
}
// final 參數(shù) - 參數(shù)不可修改
public void process(final int data, final String str) {
// data = 10; // 編譯錯誤
// str = "new"; // 編譯錯誤
System.out.println(data + ", " + str);
}
// final 局部變量
public void localFinal() {
final int local = 100;
// local = 200; // 編譯錯誤
}
}final 的內(nèi)存安全特性
public class FinalObjectDemo {
public static void main(String[] args) {
// final 對象 - 引用不可變,但內(nèi)容可變
final List<String> list = new ArrayList<>();
list.add("A"); // ? 合法:對象內(nèi)容可變
list.add("B");
list.remove(0);
// list = new ArrayList<>(); // ? 非法:引用不可重新賦值
// final 數(shù)組同理
final int[] arr = {1, 2, 3};
arr[0] = 100; // ? 合法:數(shù)組元素可變
// arr = new int[5]; // ? 非法:引用不可變
}
}static final 組合使用
常量定義的最佳實踐
/**
* 全局常量定義
*/
public final class GlobalConstants {
// 私有構(gòu)造器,防止實例化
private GlobalConstants() {}
// 編譯期常量 - 編譯器會內(nèi)聯(lián)
public static final int MAX_CONNECTIONS = 100;
public static final long TIMEOUT_MS = 5000L;
// 運行時常量 - 需要類加載
public static final String APP_VERSION = "1.0.0";
public static final String JAVA_VERSION = System.getProperty("java.version");
// 不可變集合
public static final List<String> SUPPORTED_FORMATS =
Collections.unmodifiableList(Arrays.asList("JSON", "XML", "YAML"));
// 不可變 Map
public static final Map<String, Integer> ERROR_CODES;
static {
Map<String, Integer> codes = new HashMap<>();
codes.put("NOT_FOUND", 404);
codes.put("SERVER_ERROR", 500);
ERROR_CODES = Collections.unmodifiableMap(codes);
}
}常量在內(nèi)存中的優(yōu)化
public class ConstantOptimization {
// 編譯期常量 - 會被內(nèi)聯(lián)
public static final int MAX_SIZE = 100;
// 運行時常量 - 不會內(nèi)聯(lián)
public static final String CONFIG_PATH = System.getProperty("config.path");
public static void main(String[] args) {
// 使用編譯期常量時,編譯器會直接替換為 100
// 不會觸發(fā) ConstantOptimization 類的加載
int size = ConstantOptimization.MAX_SIZE;
// 使用運行時常量時,會觸發(fā)類加載
String path = ConstantOptimization.CONFIG_PATH;
}
}類加載過程中的內(nèi)存變化
類加載的生命周期
加載 → 鏈接(驗證→準備→解析) → 初始化 → 使用 → 卸載
詳細過程解析
階段 1:加載(Loading)
JVM 讀取類的字節(jié)碼文件(.class),在內(nèi)存中生成一個代表這個類的 java.lang.Class 對象。
此時內(nèi)存中:
- 方法區(qū)開始準備存儲類的運行時數(shù)據(jù)結(jié)構(gòu)
static final的編譯期常量(如MAX_SIZE = 100)不會觸發(fā)類加載,因為已被編譯器內(nèi)聯(lián)
階段 2:鏈接(Linking)
2.1 驗證(Verification)
確保加載的類信息符合 JVM 規(guī)范,沒有安全風(fēng)險。
2.2 準備(Preparation)- 關(guān)鍵階段
為類的靜態(tài)變量分配內(nèi)存,并設(shè)置默認初始值。
public class PreparationDemo {
// 準備階段后的狀態(tài):
static int a = 10; // a = 0(零值)
static boolean b = true; // b = false(零值)
static char c = 'A'; // c = '\u0000'(零值)
static long d = 100L; // d = 0L(零值)
// 引用類型:null
static String e = "hello"; // e = null(零值)
// static final 編譯期常量:直接設(shè)置為最終值
static final int f = 20; // f = 20(最終值)
// static final 運行時常量:先設(shè)為零值
static final Random rand = new Random();
static final int g = rand.nextInt(100); // g = 0(零值)
}內(nèi)存分配:
static變量在方法區(qū)分配內(nèi)存,設(shè)置零值(0, null, false)static final的編譯期常量(如f = 20)直接設(shè)置為最終值static final的運行時常量(如g)先設(shè)為零值,初始化階段才賦真值
2.3 解析(Resolution)
將常量池中的符號引用替換為直接引用。
階段 3:初始化(Initialization)- 真正賦值
執(zhí)行 <clinit> 方法(類構(gòu)造器),按代碼順序初始化靜態(tài)變量和執(zhí)行靜態(tài)代碼塊。
public class InitializationDemo {
static int a = 10; // 第1步:a = 10
static {
System.out.println("靜態(tài)代碼塊1: a = " + a); // 第2步:輸出 a = 10
a = 20; // 第3步:a = 20
}
static int b = 30; // 第4步:b = 30
static {
System.out.println("靜態(tài)代碼塊2: a = " + a + ", b = " + b);
// 第5步:輸出 a = 20, b = 30
}
public static void main(String[] args) {
System.out.println("最終值: a = " + a + ", b = " + b);
// 第6步:輸出 最終值: a = 20, b = 30
}
}輸出結(jié)果:
靜態(tài)代碼塊1: a = 10
靜態(tài)代碼塊2: a = 20, b = 30
最終值: a = 20, b = 30
階段 4:使用(Using)
public class UsingDemo {
public static void main(String[] args) {
// 主動引用 - 會觸發(fā)類初始化
int count = Counter.getCount(); // 觸發(fā) Counter 類加載
// 被動引用 - 不會觸發(fā)類初始化
int[] arr = new int[10]; // 不會觸發(fā)數(shù)組類型的父類初始化
// 訪問編譯期常量 - 不觸發(fā)類初始化
int max = Constants.MAX_SIZE; // 不觸發(fā) Constants 類加載
// 訪問運行時常量 - 觸發(fā)類初始化
String ver = Constants.VERSION; // 觸發(fā) Constants 類加載
}
}線程安全問題
static 變量的線程安全問題
問題示例
public class UnsafeCounter {
// 非線程安全的計數(shù)器
private static int count = 0;
public static void increment() {
count++; // 非原子操作,存在競態(tài)條件
}
public static int getCount() {
return count;
}
}count++ 的實際操作:
- 讀取 count 的值
- 將 count 加 1
- 將新值寫回 count
在多線程環(huán)境下,這三個步驟可能被交叉執(zhí)行,導(dǎo)致數(shù)據(jù)不一致。
解決方案 1:使用 synchronized
public class SafeCounterWithSync {
private static int count = 0;
// 使用類鎖保護靜態(tài)變量
public synchronized static void increment() {
count++;
}
public synchronized static int getCount() {
return count;
}
}解決方案 2:使用 AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounterWithAtomic {
private static final AtomicInteger count = new AtomicInteger(0);
public static void increment() {
count.incrementAndGet(); // 原子操作
}
public static int getCount() {
return count.get();
}
}解決方案 3:使用 LongAdder(高并發(fā)場景)
import java.util.concurrent.atomic.LongAdder;
public class HighPerformanceCounter {
private static final LongAdder count = new LongAdder();
public static void increment() {
count.increment();
}
public static long getCount() {
return count.sum();
}
}static 代碼塊的線程安全
問題:靜態(tài)代碼塊只執(zhí)行一次,但初始化可能涉及復(fù)雜操作
public class UnsafeSingleton {
private static UnsafeSingleton instance;
static {
// 復(fù)雜的初始化邏輯
try {
Thread.sleep(100); // 模擬耗時操作
instance = new UnsafeSingleton();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private UnsafeSingleton() {}
public static UnsafeSingleton getInstance() {
return instance;
}
}解決方案:使用靜態(tài)內(nèi)部類(延遲加載 + 線程安全)
public class ThreadSafeSingleton {
private ThreadSafeSingleton() {}
// 靜態(tài)內(nèi)部類 - 延遲加載
private static class Holder {
private static final ThreadSafeSingleton INSTANCE = new ThreadSafeSingleton();
}
public static ThreadSafeSingleton getInstance() {
return Holder.INSTANCE;
}
}final 的線程安全特性
final 字段的初始化安全性
public class FinalExample {
private final int x;
private int y;
public FinalExample() {
x = 1; // final 字段在構(gòu)造器完成前對其他線程可見
y = 2; // 普通字段可能在對象引用逸出后仍未完成初始化
}
public static void main(String[] args) {
// 線程 A 創(chuàng)建對象
FinalExample obj = new FinalExample();
// 線程 B 直接讀取對象
// obj.x 保證可見(1),obj.y 可能是 0
}
}JMM 保證:只要對象引用對其他線程可見,那么 final 字段也一定初始化完成。
final 不可變對象的線程安全
public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// 防御性拷貝
this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}
// 只提供 getter,不提供 setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
return hobbies;
}
}static final 常量的線程安全
public class ThreadSafeConstants {
// 編譯期常量 - 線程安全(只讀)
public static final int MAX_SIZE = 100;
// 運行時常量 - 線程安全(final 保證不可變)
public static final String APP_NAME = "MyApp";
// 不可變集合 - 線程安全
public static final List<String> SAFE_LIST =
Collections.unmodifiableList(Arrays.asList("A", "B", "C"));
// 即使在多線程環(huán)境下訪問,也不需要同步
public static void main(String[] args) {
new Thread(() -> {
System.out.println(MAX_SIZE); // 安全
}).start();
new Thread(() -> {
System.out.println(APP_NAME); // 安全
}).start();
}
}常見并發(fā)陷阱
陷阱 1:誤以為 final 保證對象內(nèi)容不可變
public class FinalNotImmutable {
public static void main(String[] args) {
final Map<String, Integer> map = new HashMap<>();
map.put("A", 1); // ? 合法
// 多線程環(huán)境下修改 final 對象的內(nèi)容 - 不安全!
new Thread(() -> map.put("B", 2)).start();
new Thread(() -> map.put("C", 3)).start();
}
}解決方案:使用不可變集合
public class ImmutableMapExample {
private static final Map<String, Integer> SAFE_MAP;
static {
Map<String, Integer> temp = new HashMap<>();
temp.put("A", 1);
temp.put("B", 2);
SAFE_MAP = Collections.unmodifiableMap(temp); // 不可變
}
public static int getValue(String key) {
return SAFE_MAP.get(key); // 線程安全
}
}陷阱 2:static 初始化的可見性問題
public class StaticVisibility {
private static boolean initialized = false;
private static Data data;
static {
data = new Data(); // 初始化數(shù)據(jù)
initialized = true; // 標記已初始化
}
public static Data getData() {
if (!initialized) { // 可能讀到舊的 initialized 值
throw new IllegalStateException("Not initialized");
}
return data; // 可能 data 未完成初始化
}
}解決方案:使用 volatile 或確保類初始化的原子性
public class StaticVisibilityFixed {
// volatile 保證 happens-before 關(guān)系
private static volatile boolean initialized = false;
private static Data data;
static {
data = new Data();
initialized = true; // volatile 寫保證之前的操作對其他線程可見
}
public static Data getData() {
if (!initialized) {
throw new IllegalStateException("Not initialized");
}
return data;
}
}最佳實踐
static 使用最佳實踐
/**
* 工具類最佳實踐
*/
public final class CollectionUtils {
// 私有構(gòu)造器,防止實例化
private CollectionUtils() {
throw new AssertionError("工具類不允許實例化");
}
// 靜態(tài)工具方法
public static <T> boolean isEmpty(Collection<T> collection) {
return collection == null || collection.isEmpty();
}
public static <T> boolean isNotEmpty(Collection<T> collection) {
return !isEmpty(collection);
}
public static <T> List<T> emptyIfNull(List<T> list) {
return list == null ? Collections.emptyList() : list;
}
}final 使用最佳實踐
/**
* 不可變對象最佳實踐
*/
public final class Money {
private final long amount;
private final String currency;
public Money(long amount, String currency) {
this.amount = amount;
this.currency = Objects.requireNonNull(currency, "currency cannot be null");
}
// 返回新對象,而不是修改原對象
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount + other.amount, this.currency);
}
public long getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount == money.amount && currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}單例模式最佳實踐
/**
* 枚舉單例 - 最佳實踐
*/
public enum EnumSingleton {
INSTANCE;
private final DataService dataService;
EnumSingleton() {
this.dataService = new DataService();
}
public DataService getDataService() {
return dataService;
}
}性能對比
關(guān)鍵字組合對比
| 關(guān)鍵字組合 | 內(nèi)存分配 | 訪問速度 | 線程安全 | 適用場景 |
|---|---|---|---|---|
static | 方法區(qū),全局唯一 | 快(直接訪問) | 需額外同步 | 工具方法、共享數(shù)據(jù) |
final | 視上下文而定 | 快(編譯器優(yōu)化) | 不可變保證安全 | 不可變數(shù)據(jù)、方法鎖定 |
static final | 方法區(qū),編譯期常量可能內(nèi)聯(lián) | 最快 | 天然安全(只讀) | 全局常量、配置項 |
性能測試示例
public class PerformanceTest {
private static int staticCount = 0;
private int instanceCount = 0;
private static final int STATIC_FINAL_CONSTANT = 100;
public static void testStatic() {
staticCount++; // 直接訪問方法區(qū)
}
public void testInstance() {
instanceCount++; // 需要通過對象引用
}
public void testConstant() {
int value = STATIC_FINAL_CONSTANT; // 可能被內(nèi)聯(lián)為 int value = 100
}
}總結(jié)
核心要點
static決定歸屬:- 屬于類,所有實例共享
- 在類加載時初始化,存儲在方法區(qū)
- 訪問速度快,但多線程下需注意線程安全
final決定可變性:- 一旦初始化不可改變
- 提供初始化安全保證
- 適用于不可變對象設(shè)計
static final決定本質(zhì):- 全局常量,編譯期可能內(nèi)聯(lián)
- 天然線程安全(只讀)
- 性能最優(yōu)
- 類加載時序:
- 準備階段:靜態(tài)變量設(shè)為零值,常量設(shè)為最終值
- 初始化階段:按代碼順序執(zhí)行靜態(tài)變量賦值和靜態(tài)代碼塊
- 線程安全:
static變量:需使用synchronized、Atomic或LongAdderstatic final常量:天然線程安全final對象:引用不可變,但內(nèi)容可能需額外保護- 靜態(tài)內(nèi)部類單例:延遲加載 + 線程安全
記憶口訣
static 歸類不歸你,final 不變永相依 static final 全局常,編譯內(nèi)聯(lián)性能強 類加載時分兩步,準備初始化要清楚 線程安全需注意,原子操作不可欺
參考資料
- Java Language Specification - Classes
- Java Virtual Machine Specification - Class Loading
- Java Concurrency in Practice
- Effective Java (3rd Edition) - Chapter 4
到此這篇關(guān)于Java static 與 final關(guān)鍵字詳解的文章就介紹到這了,更多相關(guān)Java static 與 final內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Java編程中static關(guān)鍵字和final關(guān)鍵字的使用
- java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處
- java多線程關(guān)鍵字final和static詳解
- Java中final、static關(guān)鍵字與方法的重寫和繼承易錯點整理
- Java中的static和final關(guān)鍵字的使用詳解
- Java關(guān)鍵字final、static使用總結(jié)
- 學(xué)習(xí)Java的static與final關(guān)鍵字
- Java關(guān)鍵字詳解之final static this super的用法
- Java中static與final關(guān)鍵字詳解(簡單易懂)
相關(guān)文章
FeignClientFactoryBean創(chuàng)建動態(tài)代理詳細解讀
這篇文章主要介紹了FeignClientFactoryBean創(chuàng)建動態(tài)代理詳細解讀,當(dāng)直接進去注冊的方法中,一步步放下走,都是直接放bean的定義信息中放入值,然后轉(zhuǎn)成BeanDefinitionHolder,最后在注冊到IOC容器中,需要的朋友可以參考下2023-11-11
圖解Java經(jīng)典算法希爾排序的原理與實現(xiàn)
希爾排序是希爾(Donald Shell)于1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經(jīng)過改進之后的一個更高效的版本,也稱為縮小增量排序,同時該算法是沖破O(n2)的第一批算法之一。本文會以圖解的方式詳細介紹希爾排序的基本思想及其代碼實現(xiàn)2022-09-09
Eclipse下使用ANT編譯提示OutOfMemory的解決方法
由于需要使用ANT編譯的代碼比較多,特別是在第一次變異的時候,會出現(xiàn)OutOfMemory錯誤。并提示更改ANT_OPTS設(shè)定。2009-04-04
java如何使用jdbc連接TDSQL數(shù)據(jù)庫
TDSQL是騰訊打造的一款分布式數(shù)據(jù)庫產(chǎn)品,具備強一致高可用、全球部署架構(gòu)、分布式水平擴展、高性能、企業(yè)級安全等特性,本文我們就來看看Java如何使用jdbc連接TDSQL數(shù)據(jù)庫吧2025-06-06
SpringSecurity構(gòu)建基于JWT的登錄認證實現(xiàn)
這篇文章主要介紹了SpringSecurity構(gòu)建基于JWT的登錄認證實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02

