天天看點

java之常量折疊

為什麼會寫着篇部落格,因為昨天看了關于final關鍵字的解析。但是有個問題始終沒有得到解決,于是請教了我qq上之前添加的知乎大神。他給我回複的第一條消息:常量折疊。身為渣渣猿的我立馬查詢了這個概念。這是第一次知道這個概念。知乎大神還給我講了好多。讓我終于明白了這個常量折疊的概念

執行個體解析

昨天,讓我迷惑的代碼是下面這段代碼

public static void main(String[] args) {

        String a = "hello2";
        final String b = "hello";
        String d = "hello";
        String c = b + 2;
        String e = d + 2;
        System.out.println((a == c));
        System.out.println((a == e));

    }           

這段的執行結果是

true
false           

我就是不明白為什麼第一個傳回

true

呢?

留着這個疑問,我們先了解下常量折疊的概念。來更好的了解上面的代碼

常量折疊

常量折疊的概念

  • 常量折疊是一種

    編譯器優化

    技術。
  • 常量折疊主要指的是

    編譯期常量

    加減乘除的運算過程會被折疊

對于 String s1 = "1" + "2";

編譯器會給你優化成 String s1 = "12";

在生成的位元組碼中,根本看不到 "1" "2" 這兩個東西。

我們通過idea進行驗證下

1、源碼檔案

public static void main(String[] args) {
        String s1 = "1"+"2";
    }           

2、運作後,idea有個out檔案夾,找到上面檔案的class檔案

public static void main(String[] args) {
        String s1 = "12";
    }           

确實如上面所說,編譯器會給你進行優化

常量折疊發生的條件

  • 必須是編譯期常量之間進行運算才會進行常量折疊。
  • 編譯期常量就是“編譯的時候就可以确定其值的常量”,
    • 首先:字面量是

      編譯期常量

      。(數字字面量,字元串字面量等)
    • 其次:編譯期常量進行

      簡單運算的結果

      也是

      編譯期常量

      ,如1+2,"a"+"b"。
    • 最後:被編譯器常量

      指派的 final 的基本類型和字元串變量

      也是編譯期常量。

舉個栗子

1.第一個栗子

public static void main(String[] args) {
        String s1="a"+"bc";
        String s2="ab"+"c";
        System.out.println(s1 == s2);
    }           

相信大家都知道了,輸出為

true

并且隻建立了一個 "abc" 字元串對象,且位于字元串常量池中。

2、第二個栗子

public static void main(String[] args) {
        String a = "a";
        String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2);
    }           

這個結果呢?

false

s1 是字元串字面量相加,但是 s2 卻是兩個非 final 的變量相加,是以不會進行常量折疊。

而是根據 String 類特有的 + 運算符重載,變成類似這樣的代碼 (jdk1.8)

String s2 = new StringBuilder(a).append(b).toString();            

這裡toString()會生成新的String變量,顯然用 == 運算符比較是會傳回 false。

3、第三個栗子

public static void main(String[] args) {
        final String a = "a";
        final String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2);
    }           

這裡的結果就是

true

因為 被編譯器常量指派的 final 的基本類型和字元串變量也是編譯期常量

4、第四個栗子

public static void main(String[] args) {
        String x ="a";
        final String a = x;
        final String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2);
    }           

這裡的結果是

false

這裡需要注意的是:final的變量,不是被編譯期常量初始化的也不是編譯器常量

這裡的a 就不是編譯器常量

5、第五個栗子

public static void main(String[] args) {
        String x ="a";
        final String a = x;
        final String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2.intern());
    }           

true

其實,這裡大家要明白intern這個方法的意思就會明白了

// 一個字元串池,最初是空的,是由類字元串私有維護的。
1、A pool of strings, initially empty, is maintained privately by the class String. 

// 如果常量池中已經有了這個字元串,那麼直接傳回常量池中它的引用,如果沒有,那就将它的引用儲存一份到字元串常量池,然後直接傳回這個引用。
2、When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. 

3、It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.           

總結

現在看完,是不是對上面列印的結果為什麼是true 知道了呢?

是以。隻要牢記常量折疊主要指的是

編譯期常量

後續補充

上面一開始寫的是

而是根據 String 類特有的 + 運算符重載,變成類似這樣的代碼

String s2 = new StringBuffer(a).append(b).toString(); 

           

這裡更正下,就是現在我用jdk1.8 通過反編譯後先在已經不是StringBuffer 而是StringBuilder。這裡需要更正下。我也查詢了下。StringBuilder 是jdk1.5之後才有的。