最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java編譯期常量與運行時常量的區(qū)別詳解

 更新時間:2026年05月20日 08:58:06   作者:希望永不加班  
在Java開發(fā)中,常量是我們每天都會接觸的概念,從接口超時時間、業(yè)務枚舉值,到全局配置參數(shù),常量的合理使用能提升代碼可讀性、可維護性,甚至優(yōu)化程序性能,但很多同學只知道用final修飾常量,卻分不清「編譯期常量」和「運行時常量」的本質區(qū)別,本文給大家詳細說說

引言

在Java開發(fā)中,“常量”是我們每天都會接觸的概念——從接口超時時間、業(yè)務枚舉值,到全局配置參數(shù),常量的合理使用能提升代碼可讀性、可維護性,甚至優(yōu)化程序性能。但很多同學只知道用final修飾常量,卻分不清「編譯期常量」和「運行時常量」的本質區(qū)別。

比如:同樣是static final修飾的變量,為什么有的能直接被引用而不觸發(fā)類初始化?有的修改后必須重新編譯所有引用類?有的加了transient卻無效?

一、什么是Java常量?

Java中的常量,本質是「初始化后不可修改的變量」,核心約束由final關鍵字實現(xiàn)——final修飾的變量,一旦完成初始化,就無法重新賦值(基礎類型不可改值,引用類型不可改引用地址)。

根據(jù)「值確定的時機」,常量被分為兩大類型:編譯期常量(Compile-time Constant)和運行時常量(Run-time Constant),二者的底層實現(xiàn)、使用規(guī)則、性能表現(xiàn)差異極大,也是面試中常被深挖的考點。

補充:Oracle官方文檔明確規(guī)定,常量的核心判定標準是「值是否能在編譯階段確定」,這也是區(qū)分兩種常量的核心依據(jù),后續(xù)所有知識點都圍繞這一核心展開。

二、編譯期常量(Compile-time Constant)—— 編譯期確定值

2.1 定義與核心特征

編譯期常量,指的是「在Java代碼編譯階段就能確定其最終值」的常量,無需等到程序運行,編譯器就能明確其具體值,并對其進行優(yōu)化(如常量折疊)。

核心特征(必須同時滿足,缺一不可):

  • 修飾符:必須用 static final 共同修飾(接口中的字段默認被public static final修飾,因此接口中的常量默認都是編譯期常量);
  • 數(shù)據(jù)類型:只能是「基本數(shù)據(jù)類型」(byte、short、int、long、float、double、boolean、char)或「String類型」,不能是引用類型(如Integer、Object、數(shù)組等);
  • 初始化值:必須是「編譯期可計算的常量表達式」,不能依賴運行時的計算結果(如方法調(diào)用、new對象、隨機值等);
  • 底層存儲:編譯后,常量值會直接嵌入到調(diào)用類的字節(jié)碼中,同時存入方法區(qū)的運行時常量池,無需在運行時從原類中讀??;
  • 類初始化:訪問編譯期常量時,不會觸發(fā)其所在類的初始化(因為值已在編譯期確定,無需加載類即可獲取)。

2.2 合法與非法示例

合法示例(滿足所有條件,屬于編譯期常量):

// 1. 基本類型字面量(最常見)
public static final int MAX_AGE = 100;
public static final boolean FLAG = true;
public static final char CH = 'A';
public static final double PI = 3.1415926;
// 2. String字面量
public static final String NAME = "Java常量";
// 3. 編譯期可計算的表達式(僅包含編譯期常量和合法運算符)
public static final int SUM = 10 + 20; // 編譯期計算為30
public static final String COMBINE = "Hello" + "World"; // 編譯期拼接為"HelloWorld"
public static final int DIFF = MAX_AGE - 50; // 引用其他編譯期常量計算
public static final boolean LOGIC = FLAG && true; // 邏輯運算(不包含instanceof、++/--)
// 4. 接口中的常量(默認public static final)
interface Constant {
    String URL = "https://xxx.com"; // 編譯期常量
    int TIMEOUT = 3000;
}

非法示例(不滿足條件,不屬于編譯期常量):

// 1. 引用類型(即使是包裝類、枚舉,也不是編譯期常量)
public static final Integer NUM = 100; // Integer是引用類型,排除
public static final List<String> LIST = new ArrayList<>(); // 引用類型,排除
public static final EnumType TYPE = EnumType.A; // 枚舉是引用類型,排除
// 2. 初始化值依賴運行時計算
public static final int RANDOM = new Random().nextInt(); // 運行時隨機值,排除
public static final String UUID = UUID.randomUUID().toString(); // 方法調(diào)用,運行時確定
public static final int CURRENT_TIME = (int) System.currentTimeMillis(); // 運行時獲取時間
// 3. 缺少static修飾(僅final修飾,無法成為編譯期常量)
public final int AGE = 20; // 僅final,無static,屬于運行時常量
// 4. 使用非法運算符(++/--)的表達式
public static final int COUNT = 10++; // ++是運行時自增,編譯期無法計算,報錯

2.3 底層原理:編譯期優(yōu)化(常量折疊)

編譯器對編譯期常量有一個核心優(yōu)化:常量折疊(Constant Folding)—— 編譯階段,將所有涉及編譯期常量的表達式直接計算出結果,并用結果替換原表達式,減少運行時的計算開銷,提升程序性能。

舉個例子,看如下代碼:

public class CompileConstant {
    public static final int A = 5;
    public static final int B = 10;
    public static final int C = A * B + 1; // 表達式:5*10+1
}

編譯后,反編譯字節(jié)碼會發(fā)現(xiàn),C 的值已經(jīng)被直接替換為 51,原表達式 A * B + 1 會被編譯器刪除。也就是說,運行時程序直接使用 51,無需再計算表達式,這就是常量折疊的優(yōu)化效果。

補充:字符串拼接的優(yōu)化的也是同理,"Hello" + "World" 會在編譯期直接拼接為"HelloWorld",運行時無需執(zhí)行字符串拼接操作。

2.4 關鍵特性:訪問不觸發(fā)類初始化

這是編譯期常量最核心的特性,也是面試高頻考點—— 因為編譯期常量的值已經(jīng)嵌入到調(diào)用類的字節(jié)碼中,訪問時無需加載其所在的類,因此不會觸發(fā)類的初始化(不會執(zhí)行靜態(tài)代碼塊、靜態(tài)變量初始化等操作)。

示例:

// 常量類
public class ConstantClass {
    // 編譯期常量
    public static final String COMPILE_CONST = "編譯期常量";
    // 靜態(tài)代碼塊(類初始化時執(zhí)行)
    static {
        System.out.println("ConstantClass 被初始化了");
    }
}
// 測試類
public class Test {
    public static void main(String[] args) {
        // 訪問編譯期常量
        System.out.println(ConstantClass.COMPILE_CONST);
    }
}

運行結果:僅輸出 編譯期常量,不會輸出 ConstantClass 被初始化了。

原因:訪問編譯期常量時,JVM無需加載 ConstantClass,直接從當前類的字節(jié)碼中獲取常量值,因此不會觸發(fā)類的初始化。

三、運行時常量(Run-time Constant)—— 運行期確定值

3.1 定義與核心特征

運行時常量,指的是「在程序運行階段(類加載或對象實例化時)才能確定其最終值」的常量,編譯階段無法確定具體值,編譯器無法對其進行常量折疊等優(yōu)化。

核心特征(滿足任意一條即可,無需同時滿足):

  • 修飾符:可以僅用 final 修飾(實例常量),也可以用 static final 修飾(但初始化值依賴運行時計算);
  • 數(shù)據(jù)類型:可以是基本類型、String類型,也可以是引用類型(如Integer、Object、數(shù)組、枚舉等);
  • 初始化值:依賴運行時的計算結果,如方法調(diào)用、new對象、讀取配置文件、隨機值等;
  • 底層存儲:實例常量(僅final修飾)跟隨對象存儲在堆內(nèi)存中;靜態(tài)運行時常量(static final修飾)存儲在方法區(qū)的運行時常量池,但值是運行時確定的;
  • 類初始化:訪問靜態(tài)運行時常量時,會觸發(fā)其所在類的初始化(因為需要運行時確定值,必須加載類);實例運行時常量需實例化對象后才能訪問,會觸發(fā)對象初始化。

3.2 常見示例

// 1. 僅final修飾的實例常量(運行時常量)
public class RuntimeConstant {
    // 實例常量,每次new對象時初始化,值可不同
    public final int INSTANCE_CONST;
    // 構造方法中初始化(運行時確定值)
    public RuntimeConstant(int value) {
        this.INSTANCE_CONST = value;
    }
}
// 2. static final修飾,但初始化值依賴運行時計算
public class RuntimeConstant2 {
    // 運行時常量:值由方法調(diào)用確定(運行時計算)
    public static final int RANDOM_NUM = new Random().nextInt(100);
    // 運行時常量:值從配置文件讀?。ㄟ\行時加載)
    public static final String CONFIG_VALUE = readConfig("config.key");
    // 運行時常量:引用類型(枚舉)
    public static final EnumType TYPE = EnumType.B;
    // 運行時常量:包裝類(引用類型)
    public static final Integer WRAP_NUM = 100;
    // 靜態(tài)代碼塊(訪問時會觸發(fā)執(zhí)行)
    static {
        System.out.println("RuntimeConstant2 被初始化了");
    }
    // 讀取配置文件的方法(運行時執(zhí)行)
    private static String readConfig(String key) {
        // 模擬讀取配置文件
        return "config_value";
    }
}
// 3. 局部final變量(運行時常量)
public class RuntimeConstant3 {
    public void test() {
        // 局部final變量,方法執(zhí)行時初始化,屬于運行時常量
        final int LOCAL_CONST = 100;
        // 局部final變量,值由參數(shù)確定(運行時傳入)
        final String LOCAL_STR = new String("局部常量");
    }
}

3.3 關鍵特性:訪問觸發(fā)類/對象初始化

與編譯期常量相反,運行時常量的值需要在運行時確定,因此訪問時會觸發(fā)對應的初始化操作:

  • 靜態(tài)運行時常量(static final修飾):訪問時會觸發(fā)其所在類的初始化(執(zhí)行靜態(tài)代碼塊、靜態(tài)變量初始化);
  • 實例運行時常量(僅final修飾):需要先new對象(觸發(fā)對象初始化),才能訪問該常量。

實戰(zhàn)驗證(延續(xù)上面的示例):

public class Test {
    public static void main(String[] args) {
        // 訪問靜態(tài)運行時常量,觸發(fā)類初始化
        System.out.println(RuntimeConstant2.RANDOM_NUM);
    }
}

運行結果:

RuntimeConstant2 被初始化了
45(隨機值,每次運行可能不同)

原因:RANDOM_NUM 是靜態(tài)運行時常量,值由 new Random().nextInt(100) 確定(運行時計算),因此訪問時必須加載 RuntimeConstant2 類,觸發(fā)類初始化,執(zhí)行靜態(tài)代碼塊。

四、編譯期常量與運行時常量 核心區(qū)別

為了方便大家記憶和對比,整理了一張詳細的對比表,覆蓋定義、修飾符、底層、性能、初始化等核心維度,同時補充實戰(zhàn)中的關鍵差異:

對比維度

編譯期常量

運行時常量

核心定義

編譯階段確定值,編譯器可優(yōu)化

運行階段確定值,編譯器無法優(yōu)化

修飾符要求

必須是 static final 共同修飾

可僅 final,也可 static final(值依賴運行時)

數(shù)據(jù)類型

僅基本類型 + String類型

基本類型、String、引用類型(枚舉、包裝類等)均可

初始化值要求

編譯期可計算的常量表達式(字面量、合法運算)

可依賴運行時計算(方法調(diào)用、new對象、配置讀取等)

底層存儲

值嵌入調(diào)用類字節(jié)碼,同時存入運行時常量池

靜態(tài):運行時常量池;實例:堆內(nèi)存

類初始化觸發(fā)

訪問時不觸發(fā)所在類初始化

靜態(tài):訪問時觸發(fā)類初始化;實例:new對象時觸發(fā)

編譯器優(yōu)化

支持常量折疊,減少運行時開銷

無優(yōu)化,運行時計算值

transient修飾效果

無效(編譯期常量會被直接嵌入字節(jié)碼,不受transient影響)

有效(引用類型的運行時常量,加transient可排除序列化)

修改后影響范圍

修改后需重新編譯所有引用類(否則引用舊值)

修改后僅需重新編譯自身類,引用類無需重新編譯

典型使用場景

全局固定值(如PI、接口地址、枚舉字面量)

動態(tài)配置(如配置文件讀取、隨機值、對象唯一標識)

編譯期常量:編譯時確定值,不觸發(fā)類初始化,可優(yōu)化,修改需全量編譯;

運行時常量:運行時確定值,觸發(fā)初始化,無優(yōu)化,修改僅需編譯自身。

五、如何選擇兩種常量?

很多開發(fā)者濫用static final,導致出現(xiàn)“常量修改后不生效”“類初始化異常”等問題,核心是沒選對常量類型。結合企業(yè)級開發(fā)實踐,給出明確的選型建議:

5.1 優(yōu)先使用編譯期常量的場景

  • 值固定不變,且在編譯期就能確定(如數(shù)學常量、固定的業(yè)務基準值、接口固定地址);
  • 需要被多個類引用,且希望減少運行時開銷(常量折疊優(yōu)化,提升性能);
  • 不需要觸發(fā)類初始化(如工具類中的常量,避免不必要的類加載)。

示例:工具類中的常量定義

public class MathUtil {
    // 編譯期常量:固定不變,可優(yōu)化
    public static final double PI = 3.1415926;
    public static final int DEFAULT_SCALE = 2;
    public static final String EMPTY_STR = "";
}

5.2 優(yōu)先使用運行時常量的場景

  • 值需要動態(tài)確定(如從配置文件讀取、數(shù)據(jù)庫查詢、隨機生成、方法返回值);
  • 常量是引用類型(如枚舉、包裝類、數(shù)組、對象);
  • 每個對象需要獨立的常量值(如對象的唯一標識、實例級別的固定配置);
  • 常量值可能會修改,且不想重新編譯所有引用類(降低維護成本)。

示例:配置類中的運行時常量

public class ConfigConstant {
    // 運行時常量:從配置文件讀?。▌討B(tài)確定值)
    public static final String DB_URL = ConfigLoader.load("db.url");
    public static final int DB_PORT = Integer.parseInt(ConfigLoader.load("db.port"));
    // 運行時常量:引用類型(枚舉)
    public static final DataSourceType DATA_SOURCE_TYPE = DataSourceType.MYSQL;
    // 實例運行時常量:每個對象獨立值
    public final String INSTANCE_ID;
    public ConfigConstant(String instanceId) {
        this.INSTANCE_ID = instanceId;
    }
}

六、注意事項

1:誤以為“static final修飾的都是編譯期常量”

錯誤認知:只要用static final修飾,就是編譯期常量。

錯誤示例:

// 錯誤:認為這是編譯期常量,實際是運行時常量
public static final Integer NUM = 100;
public static final String UUID = UUID.randomUUID().toString();

原因:Integer是引用類型,UUID的值由方法調(diào)用確定(運行時),因此這兩個都是運行時常量,訪問時會觸發(fā)類初始化,且不支持常量折疊。

正確做法:判斷是否為編譯期常量,不僅看修飾符,還要看「數(shù)據(jù)類型」和「初始化值是否可編譯期確定」。

2:編譯期常量修改后,引用類未重新編譯,導致舊值殘留

場景:類A定義了編譯期常量MAX_NUM = 100,類B引用了A.MAX_NUM;修改A類的MAX_NUM = 200,僅重新編譯A類,未編譯B類,運行時B類仍使用舊值100。

原因:編譯期常量的值會嵌入到引用類的字節(jié)碼中,B類編譯后,字節(jié)碼中已經(jīng)是100,修改A類后,若不重新編譯B類,B類會一直使用嵌入的舊值。

避坑方案:修改編譯期常量后,必須重新編譯所有引用該常量的類;若常量值可能頻繁修改,建議改為運行時常量(從配置文件讀取)。

3:用transient修飾編譯期常量,誤以為能排除序列化

錯誤示例:

public class SerializeTest implements Serializable {
    // 錯誤:transient修飾編譯期常量,無效
    private transient static final String SECRET = "123456";
}

原因:編譯期常量會被直接嵌入字節(jié)碼,序列化時不受transient影響,即使加了transient,序列化后仍能獲取到常量值。

正確做法:若想排除常量的序列化,不要用transient(對編譯期常量無效),可實現(xiàn)Externalizable接口,手動控制不寫入該字段。

4:局部final變量誤認為是編譯期常量

錯誤示例:

public void test() {
    final int a = new Random().nextInt();
    // 錯誤:認為a是編譯期常量,實際是運行時常量
    System.out.println(a + 10);
}

原因:局部final變量的初始化值若依賴運行時計算,就是運行時常量,編譯器無法對其進行常量折疊優(yōu)化;只有局部final變量的初始化值是字面量或編譯期可計算表達式,才會被編譯器優(yōu)化。

5:接口中的常量不是編譯期常量

錯誤示例:

interface MyConstant {
    // 錯誤:認為這是編譯期常量,實際是運行時常量
    String CONFIG = readConfig();
    static String readConfig() {
        return "config";
    }
}

原因:接口中的常量默認是public static final,但初始化值readConfig()是方法調(diào)用(運行時確定),因此是運行時常量,訪問時會觸發(fā)接口的初始化。

七、全文總結

1. 兩種常量的核心區(qū)別:值確定的時機(編譯期 vs 運行期);

2. 編譯期常量:static final + 基本/String + 編譯期表達式,不觸發(fā)類初始化,支持常量折疊;

3. 運行時常量:可僅final,支持引用類型,值依賴運行時計算,觸發(fā)初始化;

4. 坑點核心:不要混淆static final和編譯期常量,修改編譯期常量需全量編譯;

5. 選型原則:固定值用編譯期,動態(tài)值用運行期;

6. 面試關鍵:類初始化觸發(fā)、常量折疊、transient效果、修改后影響范圍。

編譯期常量與運行時常量,看似簡單,卻藏著JVM底層優(yōu)化和開發(fā)細節(jié),也是大廠面試中區(qū)分“初級開發(fā)者”和“中級開發(fā)者”的關鍵考點。

很多開發(fā)者因為分不清二者,導致出現(xiàn)“常量修改不生效”“類初始化異常”“序列化漏洞”等問題,看完這篇,基本能避開所有高頻坑,同時應對所有相關面試題。

以上就是Java編譯期常量與運行時常量的區(qū)別詳解的詳細內(nèi)容,更多關于Java編譯期常量與運行時常量的資料請關注腳本之家其它相關文章!

相關文章

最新評論

安福县| 汨罗市| 射阳县| 托克逊县| 易门县| 丹东市| 仁布县| 治县。| 谢通门县| 琼中| 响水县| 周口市| 宝应县| 通化县| 于田县| 贵南县| 右玉县| 九台市| 库车县| 赤水市| 从江县| 沂源县| 池州市| 克拉玛依市| 石阡县| 绥宁县| 深水埗区| 克东县| 关岭| 吴堡县| 昭觉县| 利辛县| 邮箱| 永州市| 中西区| 靖安县| 尼勒克县| 蒙城县| 平凉市| 镇平县| 博客|