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

Java編譯期常量與運(yùn)行時(shí)常量的區(qū)別詳解

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

引言

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

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

一、什么是Java常量?

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

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

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

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

2.1 定義與核心特征

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

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

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

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. 編譯期可計(jì)算的表達(dá)式(僅包含編譯期常量和合法運(yùn)算符)
public static final int SUM = 10 + 20; // 編譯期計(jì)算為30
public static final String COMBINE = "Hello" + "World"; // 編譯期拼接為"HelloWorld"
public static final int DIFF = MAX_AGE - 50; // 引用其他編譯期常量計(jì)算
public static final boolean LOGIC = FLAG && true; // 邏輯運(yùn)算(不包含instanceof、++/--)
// 4. 接口中的常量(默認(rèn)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. 初始化值依賴運(yùn)行時(shí)計(jì)算
public static final int RANDOM = new Random().nextInt(); // 運(yùn)行時(shí)隨機(jī)值,排除
public static final String UUID = UUID.randomUUID().toString(); // 方法調(diào)用,運(yùn)行時(shí)確定
public static final int CURRENT_TIME = (int) System.currentTimeMillis(); // 運(yùn)行時(shí)獲取時(shí)間
// 3. 缺少static修飾(僅final修飾,無法成為編譯期常量)
public final int AGE = 20; // 僅final,無static,屬于運(yùn)行時(shí)常量
// 4. 使用非法運(yùn)算符(++/--)的表達(dá)式
public static final int COUNT = 10++; // ++是運(yùn)行時(shí)自增,編譯期無法計(jì)算,報(bào)錯(cuò)

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

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

舉個(gè)例子,看如下代碼:

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

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

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

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

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

示例:

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

運(yùn)行結(jié)果:僅輸出 編譯期常量,不會(huì)輸出 ConstantClass 被初始化了。

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

三、運(yùn)行時(shí)常量(Run-time Constant)—— 運(yùn)行期確定值

3.1 定義與核心特征

運(yùn)行時(shí)常量,指的是「在程序運(yùn)行階段(類加載或?qū)ο髮?shí)例化時(shí))才能確定其最終值」的常量,編譯階段無法確定具體值,編譯器無法對(duì)其進(jìn)行常量折疊等優(yōu)化。

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

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

3.2 常見示例

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

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

與編譯期常量相反,運(yùn)行時(shí)常量的值需要在運(yùn)行時(shí)確定,因此訪問時(shí)會(huì)觸發(fā)對(duì)應(yīng)的初始化操作:

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

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

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

運(yùn)行結(jié)果:

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

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

四、編譯期常量與運(yùn)行時(shí)常量 核心區(qū)別

為了方便大家記憶和對(duì)比,整理了一張?jiān)敿?xì)的對(duì)比表,覆蓋定義、修飾符、底層、性能、初始化等核心維度,同時(shí)補(bǔ)充實(shí)戰(zhàn)中的關(guān)鍵差異:

對(duì)比維度

編譯期常量

運(yùn)行時(shí)常量

核心定義

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

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

修飾符要求

必須是 static final 共同修飾

可僅 final,也可 static final(值依賴運(yùn)行時(shí))

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

僅基本類型 + String類型

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

初始化值要求

編譯期可計(jì)算的常量表達(dá)式(字面量、合法運(yùn)算)

可依賴運(yùn)行時(shí)計(jì)算(方法調(diào)用、new對(duì)象、配置讀取等)

底層存儲(chǔ)

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

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

類初始化觸發(fā)

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

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

編譯器優(yōu)化

支持常量折疊,減少運(yùn)行時(shí)開銷

無優(yōu)化,運(yùn)行時(shí)計(jì)算值

transient修飾效果

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

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

修改后影響范圍

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

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

典型使用場(chǎng)景

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

動(dòng)態(tài)配置(如配置文件讀取、隨機(jī)值、對(duì)象唯一標(biāo)識(shí))

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

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

五、如何選擇兩種常量?

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

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

  • 值固定不變,且在編譯期就能確定(如數(shù)學(xué)常量、固定的業(yè)務(wù)基準(zhǔn)值、接口固定地址);
  • 需要被多個(gè)類引用,且希望減少運(yùn)行時(shí)開銷(常量折疊優(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)先使用運(yùn)行時(shí)常量的場(chǎng)景

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

示例:配置類中的運(yùn)行時(shí)常量

public class ConfigConstant {
    // 運(yùn)行時(shí)常量:從配置文件讀取(動(dòng)態(tài)確定值)
    public static final String DB_URL = ConfigLoader.load("db.url");
    public static final int DB_PORT = Integer.parseInt(ConfigLoader.load("db.port"));
    // 運(yùn)行時(shí)常量:引用類型(枚舉)
    public static final DataSourceType DATA_SOURCE_TYPE = DataSourceType.MYSQL;
    // 實(shí)例運(yùn)行時(shí)常量:每個(gè)對(duì)象獨(dú)立值
    public final String INSTANCE_ID;
    public ConfigConstant(String instanceId) {
        this.INSTANCE_ID = instanceId;
    }
}

六、注意事項(xiàng)

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

錯(cuò)誤認(rèn)知:只要用static final修飾,就是編譯期常量。

錯(cuò)誤示例:

// 錯(cuò)誤:認(rèn)為這是編譯期常量,實(shí)際是運(yùn)行時(shí)常量
public static final Integer NUM = 100;
public static final String UUID = UUID.randomUUID().toString();

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

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

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

場(chǎng)景:類A定義了編譯期常量MAX_NUM = 100,類B引用了A.MAX_NUM;修改A類的MAX_NUM = 200,僅重新編譯A類,未編譯B類,運(yùn)行時(shí)B類仍使用舊值100。

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

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

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

錯(cuò)誤示例:

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

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

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

4:局部final變量誤認(rèn)為是編譯期常量

錯(cuò)誤示例:

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

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

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

錯(cuò)誤示例:

interface MyConstant {
    // 錯(cuò)誤:認(rèn)為這是編譯期常量,實(shí)際是運(yùn)行時(shí)常量
    String CONFIG = readConfig();
    static String readConfig() {
        return "config";
    }
}

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

七、全文總結(jié)

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

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

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

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

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

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

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

很多開發(fā)者因?yàn)榉植磺宥?,?dǎo)致出現(xiàn)“常量修改不生效”“類初始化異常”“序列化漏洞”等問題,看完這篇,基本能避開所有高頻坑,同時(shí)應(yīng)對(duì)所有相關(guān)面試題。

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

相關(guān)文章

最新評(píng)論

芷江| 股票| 镇巴县| 大竹县| 斗六市| 盈江县| 隆化县| 新密市| 邵武市| 江北区| 于田县| 南开区| 牙克石市| 绩溪县| 南汇区| 英超| 资源县| 依安县| 平凉市| 南川市| 吉安县| 普陀区| 泸定县| 定襄县| 鹤峰县| 读书| 宜丰县| 雷州市| 道真| 星子县| 博湖县| 侯马市| 罗源县| 远安县| 仪陇县| 嘉峪关市| 岚皋县| 张家界市| 日照市| 宁乡县| 贵溪市|