Java中short, byte, boolean和char的擴(kuò)展時(shí)機(jī)

擴(kuò)展概述

眾所周知的是,在Java中,指令的操作碼是由一個(gè)字節(jié)組成的,這意味著操作碼的取值范圍在0-255之間。由此帶來(lái)了一個(gè)問(wèn)題,對(duì)于部分和類型相關(guān)的指令——比如load——來(lái)說(shuō),并不能做到給每一個(gè)類型都設(shè)計(jì)一個(gè)對(duì)應(yīng)的指令。而在Java虛擬機(jī)中,針對(duì)short, byte, boolean和char都是使用int類型的指令來(lái)完成的。舉個(gè)例子來(lái)說(shuō),從局部變量表里面加載一個(gè)short類型的數(shù)據(jù)到操作數(shù)棧,將會(huì)使用iload指令。
Java虛擬機(jī)對(duì)此有明確的說(shuō)明。但是這又會(huì)有另外一個(gè)問(wèn)題:short, byte, boolean和char都是“短”類型,它們的字節(jié)數(shù)都要比int類型的少。所以在使用int類型的指令的時(shí)候,就需要解決擴(kuò)展的問(wèn)題。擴(kuò)展的問(wèn)題可以分成兩點(diǎn):

  • 如何擴(kuò)展
  • 什么時(shí)候擴(kuò)展

第一個(gè)問(wèn)題,如何擴(kuò)展,Java虛擬機(jī)規(guī)范指出short和byte兩種類型將執(zhí)行符號(hào)擴(kuò)展,而boolean和char類型將執(zhí)行零擴(kuò)展。
而第二個(gè)問(wèn)題,什么時(shí)候擴(kuò)展,Java虛擬機(jī)規(guī)范只有一句含糊的“可以在編譯期或者運(yùn)行期”。這似乎意味著虛擬機(jī)的實(shí)現(xiàn)可以自己決定什么時(shí)候擴(kuò)展。但這有點(diǎn)一廂情愿,因?yàn)樵谔摂M機(jī)規(guī)范中的指令說(shuō)明中,其實(shí)就已經(jīng)限定了擴(kuò)展的時(shí)機(jī)。
本文將主要探討一下在Oracle JDK 1.7.0_80版本下,使用Hotspot虛擬機(jī)的情況下,Java中short, byte, boolean和char的擴(kuò)展時(shí)機(jī)。

直覺(jué)猜想

一種直覺(jué)上的認(rèn)知是,擴(kuò)展會(huì)帶來(lái)性能的損耗,因此能夠在編譯期擴(kuò)展完成,就沒(méi)有必要在運(yùn)行期擴(kuò)展?;谶@樣的想法,可以提出一個(gè)猜想:所有的字面量,都會(huì)在編譯期完成擴(kuò)展。而除了字面量以外,其余的變量、參數(shù)等,因?yàn)樵诰幾g期無(wú)法確定其值,因此無(wú)法在編譯期完成擴(kuò)展。
早前我在思考這個(gè)問(wèn)題的時(shí)候陷入了一個(gè)誤區(qū):所有的boolean和char類型的值(包括變量等)都能夠在編譯期完成擴(kuò)展。這是基于這兩個(gè)類型使用無(wú)符號(hào)擴(kuò)展所產(chǎn)生的。這兩個(gè)類型執(zhí)行無(wú)符號(hào)擴(kuò)展意味著,不論它們的確切值是什么,總可以將高位置為0而完成擴(kuò)展。與之對(duì)應(yīng)的是,因?yàn)閟hort和byte執(zhí)行符號(hào)擴(kuò)展,在編譯期無(wú)法確定其符號(hào)的情況下,則不能實(shí)現(xiàn)擴(kuò)展。
這個(gè)想法之所以錯(cuò)誤是因?yàn)闆](méi)有考慮到程序被編譯成字節(jié)碼之后的本質(zhì)。對(duì)于字節(jié)碼來(lái)說(shuō),不存在一個(gè)操作數(shù),其高位是確定的(執(zhí)行擴(kuò)展得來(lái)的),而低位是不確定的。所有的操作數(shù),在編譯期都是確定無(wú)誤的。
因此問(wèn)題最終就歸結(jié)為:是否所有的字面量,都是在編譯期完成擴(kuò)展?

分析

是否所有的字面量,都在編譯期完成了擴(kuò)展?
很顯然,答案是否定的。
這里采用char作為例子來(lái)說(shuō)明:

public class CharExtend {
    private static final char a=0x1234;//4660
    private static char b=0x1357;//4951
    private final char c=0x2468;//9320
    private char d=0x9876;//39030

    public char testChar(char g, final char h){
        char e=0x9753;//38739
        final char f=0x8642;//34370
        b=a+c;
        d=a+c;
        d+=a;
        d+=f;
        b+=d;
        b+=h;
        b+=e;
        g+=b;
        return g;
    }

    public static char testChar1(char i, final char j, final char k){
        char l=0x4567;//17767
        final char m=0xabcd;//43981
        b+=a;
        b+=i;
        b+=j;
        b+=k;
        b+=l;
        b+=m;
        return b;
    }
}

因?yàn)椴淮_定final,static關(guān)鍵字是否會(huì)對(duì)擴(kuò)展造成影響,所以需要盡可能的覆蓋這些關(guān)鍵字的所有的組合。
要想確定它們的擴(kuò)展時(shí)間,還需要閱讀它們生成的字節(jié)碼文件。這并不是指閱讀javap命令所生成的內(nèi)容,而是指,直接讀二進(jìn)制內(nèi)容。因?qū)avap命令會(huì)屏蔽掉這個(gè)擴(kuò)展的信息。舉個(gè)例子來(lái)說(shuō),假如有一個(gè)字面量1,那么javap解析出來(lái)的只會(huì)是1,但是看不出來(lái)它是0x00000001還是0x0001。不過(guò)因?yàn)槎M(jìn)制文件讀起來(lái)十分困難,可以使用javap解析的內(nèi)容進(jìn)行輔助。
這個(gè)類編譯生成的二進(jìn)制內(nèi)容和javap解析得到的內(nèi)容此處就不貼了。下面對(duì)a進(jìn)行分析: a的十六進(jìn)制是0x1234,在二進(jìn)制中出現(xiàn)了三次:

0300 0012 3401 0001 6201 0001 6303 0000
b500 042a 59b4 0004 1112 3460 92b5 0004
0836 04b2 0007 1112 3460 92b3 0007 b200
  • 第一次是出現(xiàn)在常量池,對(duì)應(yīng)的二進(jìn)制是0x00001234,其前面的03是常量池中Integer類型的表示。可以看到的是,它已經(jīng)被擴(kuò)展了;
  • 第二次和第三次都是出現(xiàn)在0x111234這樣一個(gè)串中,而11是sipush指令。在這兩次出現(xiàn)中a并沒(méi)有被擴(kuò)展;

對(duì)其余變量、參數(shù)的分析這里不一一說(shuō)明??傮w上可以觀察得到:

  • 出現(xiàn)在常量池中的都已經(jīng)被擴(kuò)展了;
  • 出現(xiàn)在類文件的Code屬性中,使用bipush, sipush指令的,都沒(méi)有被擴(kuò)展;
  • 一個(gè)字面量可能同時(shí)出現(xiàn)在常量池和Code中;
  • static的修飾對(duì)擴(kuò)展時(shí)機(jī)沒(méi)有影響;
  • final修飾的成員變量的字面量,必然會(huì)出現(xiàn)在常量池中,而作為局部變量或者參數(shù)的修飾,則不具有這種效果;

所以問(wèn)題可以進(jìn)一步歸結(jié)為:什么因素會(huì)影響編譯器決定將字面量放入常量池,或者放入Code屬性?這個(gè)問(wèn)題,Java虛擬機(jī)規(guī)范沒(méi)有太多的信息。唯一和此有關(guān)的就是:對(duì)于short, byte, boolean, char以及小的int類型的值來(lái)說(shuō),可以使用bipush, sipush或者iconst指令。這三種指令都明確指出,擴(kuò)展是在指令執(zhí)行的時(shí)候同時(shí)進(jìn)行的。而大的值就會(huì)出現(xiàn)在常量池中。

結(jié)論

第一,是否在編譯期擴(kuò)展和值的大小有關(guān),值如果偏大,那么編譯器會(huì)在常量池中存有該字面量。而在常量池中字面量,必然是被擴(kuò)展了的;
第二,有final關(guān)鍵字修飾的成員變量,其值必然會(huì)被擴(kuò)展,并且放入常量池;
第三,對(duì)于一個(gè)偏小的值,但是又屬于final修飾的成員變量,那么它會(huì)存有一份擴(kuò)展了的常量池副本,也會(huì)在Code屬性中保持未擴(kuò)展的形式;
第四,其余情況都只能在運(yùn)行時(shí)擴(kuò)展;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容