今天偶然遇到一個編譯時常量(Compile-time Constant)和運行時常量(Run-time Constant)的題目,所以希望通過寫一篇博文, 來仔細記錄下所有的區別和優劣.
因水平有限, 大部分內容都是查閱資料和其他人的博客來的.
問題起源
首先, 源於在博客https://blog.csdn.net/hzw19920329/article/details/51055736 看到了這個代碼
public class GetClass { public static void main(String[] args) { System.out.println(Test.name);//① System.out.println(Test.score);//② System.out.println(Test.age);//③ } } class Test { public static int age = 23; public static final String name = "shanxi"; public static final Integer score = 85; static { System.out.println("Test static block"); } }
單獨運行①輸出的結果是: shanxi
單獨運行②輸出的結果是:
Test static block
85
單獨運行③輸出的結果是:
Test static block
23
於是開始思考,為什么會這樣,原博主也解釋了說是"可以看出雖然兩者都是static final 類型的,但是name作為"編譯常量",他是不需要對類Test進行初始化就可以讀取,因而不會執行Test中的靜態代碼塊;但是score雖然作為static final變量,但是他並不是"編譯常量",需要初始化Test類之后才可以,因而會首先執行static代碼塊,隨后輸出score的值;對於非final的static域,那么對他訪問之前要先進行鏈接(為這個域初始化空間)和初始化(初始化該存儲空間),也就明白了單獨運行③會首先輸出static塊的值原因;"
開始解決
第一步, 什么是編譯常量
Oracle的官方文檔 15.28 Constant Expressions 詳細的解釋了,什么是 Compile-time Constant
1. 原始類型字面量,或者String字面量 2. 能轉型為原始類型字面量,或String字面量的常量 3. 一元運算符(+,-,~,!,但不包含++, --) 和1,2組成的表達式 4. 多元運算符(*,/和%)和1,2組成的表達式 5. 附加運算符( additive operators) (+ 或 -)與之前幾條組成的表達式 6. 位移運算符(<<,>>, >>>)和之前幾條組成的表達式 7. 關系運算符(<,<=,>,>= ,不包括 instanceof)與之前幾條組成的表達式 8. 關系運算符(==,!=)與之前幾條組成的表達式 9. 位運算符(&, ^, |)與之前幾條組成的表達式 10. 條件與和條件或運算符(&&, ||) 與之前幾條組成的表達式 11. 三元運算符 (?:)和之前幾條組成的表達式 12. 帶括號的表達式,括號內也是常量表達式 13. 引用常量變量的簡單變量 14. 類中的常量變量引用,使用類的全限定名或類名進行引用(String.class)
針對其簡單解釋其中幾個
1, 編譯時常量必須定義為基本類型或者String,
基本類型指的是int, long, short, boolean, char, float, double
所以, 如果Test類中如果再定義一個public static final int b=2; 那么Test.b輸出時,一樣不會加載Test類, 而是直接輸出2.不會輸出靜態代碼塊.
2, 編譯時常量也可以是基本類型加上部分運算符, 上面的規則我基本都試過了, 都是對的. 其實只要記住, 運算符只要不是instanceof和自加, 自減運算符就基本都可以. 這樣在筆試的時候, 可以記得更清楚.
3.其實上面介紹中少了最重要的一項, 變量必須是用final修飾的, 而且變量必須在聲明的同時進行賦值(其實這是final修飾詞的要求)
編譯時常量是否一定需要static修飾?
這個問題值得深究, 分兩方面來解釋這個問題
- 如果是要外部調用, 那沒有static是不行的, 這個很好解釋, 如果沒有static, 那么想要得到這個常量,必須得new出來對象才行, 所以外部調用時, 必須要用static修飾
- 內部使用, 內部使用時, static為非必須存在的.
可能的面試題: 編譯時常量存在什么樣的風險?
(CSDN中別人這么說的)
編譯時常量在編譯的時候會被直接寫成對應的值, 而不會再從原來的類中讀取, 這樣就會導致問題的產生:
如果A類定義了常量, B類使用了常量, 並且都進行了編譯, 當A類的源碼被改動了, 常量的值發生了變化, 我們對A類進行重新編譯, 但是沒有對B進行重新編譯, 那么B類中用到的是原來A類中的常量值, 即舊值, 這樣就導致了風險的產生.
但是理論上是這樣的, 實際上操作中IDE會自動幫我們重新編譯B類, 所以我試着操作了幾次, 並沒有發生這樣的風險,
但是這個風險的確是存在的.