<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          String s="a"+"b"+"c",到底創(chuàng)建了幾個對象?

          共 4765字,需瀏覽 10分鐘

           ·

          2021-09-04 19:52

          首先看一下這道常見的面試題,下面代碼中,會創(chuàng)建幾個字符串對象?

          String s="a"+"b"+"c";

          如果你比較一下Java源代碼和反編譯后的字節(jié)碼文件,就可以直觀的看到答案,只創(chuàng)建了一個String對象。

          估計大家會有疑問了,為什么源代碼中字符串拼接的操作,在編譯完成后會消失,直接呈現(xiàn)為一個拼接后的完整字符串呢?

          這是因為在編譯期間,應(yīng)用了編譯器優(yōu)化中一種被稱為常量折疊(Constant Folding)的技術(shù),會將編譯期常量的加減乘除的運(yùn)算過程在編譯過程中折疊。編譯器通過語法分析,會將常量表達(dá)式計算求值,并用求出的值來替換表達(dá)式,而不必等到運(yùn)行期間再進(jìn)行運(yùn)算處理,從而在運(yùn)行期間節(jié)省處理器資源。

          而上邊提到的編譯期常量的特點(diǎn)就是它的值在編譯期就可以確定,并且需要完整滿足下面的要求,才可能是一個編譯期常量:

          • 被聲明為final
          • 基本類型或者字符串類型
          • 聲明時就已經(jīng)初始化
          • 使用常量表達(dá)式進(jìn)行初始化

          上面的前兩條比較容易理解,需要注意的是第三和第四條,通過下面的例子進(jìn)行說明:

          final String s1="hello "+"Hydra";
          final String s2=UUID.randomUUID().toString()+"Hydra";

          編譯器能夠在編譯期就得到s1的值是hello Hydra,不需要等到程序的運(yùn)行期間,因此s1屬于編譯期常量。而對s2來說,雖然也被聲明為final類型,并且在聲明時就已經(jīng)初始化,但使用的不是常量表達(dá)式,因此不屬于編譯期常量,這一類型的常量被稱為運(yùn)行時常量。再看一下編譯后的字節(jié)碼文件中的常量池區(qū)域:

          可以看到常量池中只有一個String類型的常量hello Hydra,而s2對應(yīng)的字符串常量則不在此區(qū)域。對編譯器來說,運(yùn)行時常量在編譯期間無法進(jìn)行折疊,編譯器只會對嘗試修改它的操作進(jìn)行報錯處理。

          另外值得一提的是,編譯期常量與運(yùn)行時常量的另一個不同就是是否需要對類進(jìn)行初始化,下面通過兩個例子進(jìn)行對比:

          public class IntTest1 {
              public static void main(String[] args) {
                  System.out.println(a1.a);
              }
          }
          class a1{
              static {
                  System.out.println("init class");
              }
              public static int a=1;
          }

          運(yùn)行上面的代碼,輸出:

          init class
          1

          如果對上面進(jìn)行修改,對變量a添加final進(jìn)行修飾:

          public static final int a=1;

          再次執(zhí)行上面的代碼,會輸出:

          1

          可以看到在添加了final修飾后,兩次運(yùn)行的結(jié)果是不同的,這是因為在添加final后,變量a成為了編譯期常量,不會導(dǎo)致類的初始化。另外,在聲明編譯器常量時,final關(guān)鍵字是必要的,而static關(guān)鍵字是非必要的,上面加static修飾只是為了驗證類是否被初始化過。

          我們再看幾個例子來加深對final關(guān)鍵字的理解,運(yùn)行下面的代碼:

          public static void main(String[] args) {
              final String h1 = "hello";
              String h2 = "hello";
              String s1 = h1 + "Hydra";
              String s2 = h2 + "Hydra";
              System.out.println((s1 == "helloHydra"));
              System.out.println((s2 == "helloHydra"));
          }

          執(zhí)行結(jié)果:

          true
          false

          代碼中字符串h1h2都使用常量賦值,區(qū)別在于是否使用了final進(jìn)行修飾,對比編譯后的代碼,s1進(jìn)行了折疊而s2沒有,可以印證上面的理論,final修飾的字符串變量屬于編譯期常量。

          再看一段代碼,執(zhí)行下面的程序,結(jié)果會返回什么呢?

          public static void main(String[] args) {
              String h ="hello";
              final String h2 = h;
              String s = h2 + "Hydra";
              System.out.println(s=="helloHydra");
          }

          答案是false,因為雖然這里字符串h2final修飾,但是初始化時沒有使用編譯期常量,因此它也不是編譯期常量。

          在上面的一些例子中,在執(zhí)行常量折疊的過程中都遵循了使用常量表達(dá)式進(jìn)行初始化這一原則,這里可能有的同學(xué)還會有疑問,到底什么樣才能算得上是常量表達(dá)式呢?在Oracle官網(wǎng)的文檔中,列舉了很多種情況,下面對常見的情況進(jìn)行列舉(除了下面這些之外官方文檔上還列舉了不少情況,如果有興趣的話,可以自己查看):

          • 基本類型和String類型的字面量
          • 基本類型和String類型的強(qiáng)制類型轉(zhuǎn)換
          • 使用+-!等一元運(yùn)算符(不包括++--)進(jìn)行計算
          • 使用加減運(yùn)算符+-,乘除運(yùn)算符*/% 進(jìn)行計算
          • 使用移位運(yùn)算符 >><<>>>進(jìn)行位移操作
          • ……

          字面量(literals)是用于表達(dá)源代碼中一個固定值的表示法,在Java中創(chuàng)建一個對象時需要使用new關(guān)鍵字,但是給一個基本類型變量賦值時不需要使用new關(guān)鍵字,這種方式就可以被稱為字面量。Java中字面量主要包括了以下類型的字面量:

          //整數(shù)型字面量:
          long l=1L;
          int i=1;

          //浮點(diǎn)類型字面量:
          float f=11.1f;
          double d=11.1;

          //字符和字符串類型字面量:
          char c='h';
          String s="Hydra";

          //布爾類型字面量:
          boolean b=true;

          當(dāng)我們在代碼中定義并初始化一個字符串對象后,程序會在常量池(constant pool)中緩存該字符串的字面量,如果后面的代碼再次用到這個字符串的字面量,會直接使用常量池中的字符串字面量。

          除此之外,還有一類比較特殊的null類型字面量,這個類型的字面量只有一個就是null,這個字面量可以賦值給任意引用類型的變量,表示這個引用類型變量中保存的地址為空,也就是還沒有指向任何有效的對象。

          那么,如果不是使用的常量表達(dá)式進(jìn)行初始化,在變量的初始化過程中引入了其他變量(且沒有被final修飾)的話,編譯器會怎樣進(jìn)行處理呢?我們下面再看一個例子:

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

          結(jié)果打印:

          false
          true

          為什么會出現(xiàn)不同的結(jié)果?在Java中,String類型在使用==進(jìn)行比較時,是判斷的引用是否指向堆內(nèi)存中的同一塊地址,出現(xiàn)上面的結(jié)果那么說明指向的不是內(nèi)存中的同一塊地址。

          通過之前的分析,我們知道s3會進(jìn)行常量折疊,引用的是常量池中的ab,所以相等。而字符串s2在進(jìn)行拼接時,表達(dá)式中引用了其他對象,不屬于編譯期常量,因此不能進(jìn)行折疊。

          那么,在沒有常量折疊的情況下,為什么最后返回的是false呢?我們看一下這種情況下,編譯器是如何實(shí)現(xiàn),先執(zhí)行下面的代碼:

          public static void main(String[] args) {
              String s1="my ";
              String s2="name ";
              String s3="is ";
              String s4="Hydra";
              String s=s1+s2+s3+s4;
          }

          然后使用javap對字節(jié)碼文件進(jìn)行反編譯,可以看到在這一過程中,編譯器同樣會進(jìn)行優(yōu)化:

          可以看到,雖然我們在代碼中沒有顯示的調(diào)用StringBuilder,但是在字符串拼接的場景下,Java編譯器會自動進(jìn)行優(yōu)化,新建一個StringBuilder對象,然后調(diào)用append方法進(jìn)行字符串的拼接。而在最后,調(diào)用了StringBuildertoString方法,生成了一個新的字符串對象,而不是引用的常量池中的常量。這樣,也就能解釋為什么在上面的例子中,s2=="ab"會返回false了。

          本文代碼基于Java 1.8.0_261-b12 版本測試

          關(guān)注公眾號【Java技術(shù)江湖】后回復(fù)“PDF”即可領(lǐng)取200+頁的《Java工程師面試指南》

          強(qiáng)烈推薦,幾乎涵蓋所有Java工程師必知必會的知識點(diǎn),不管是復(fù)習(xí)還是面試,都很實(shí)用。



          瀏覽 19
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产婷婷综合视频网站 | 高清一级片在线播放 | www啊啊啊 | 中文字幕不卡在线 | 日韩高清无码一区 |