關(guān)于 int 和 Integer 不得不說的事

任何一個(gè)學(xué)過 Java 的人茸歧,肯定知道 int 是原始數(shù)據(jù)類型,Integer 是一個(gè)對(duì)象显沈,他們之間可以自動(dòng)地拆箱裝箱举娩。但,如果繼續(xù)挖掘构罗,仍然大有分析地余地铜涉。你,真的懂 int 和 Integer 了嗎遂唧?

關(guān)于自動(dòng)拆箱和裝箱

我們都知道 int 和 Integer 可以自動(dòng)相互轉(zhuǎn)換芙代,這是 Java 給我們提供的一種語法糖,但是盖彭,它是“怎么”轉(zhuǎn)換的纹烹?在什么時(shí)候轉(zhuǎn)換?是編譯期還是運(yùn)行期召边?

眼見為實(shí)铺呵,讓我們編寫一段代碼來實(shí)際看看過程

public class TestClass {
    
    public void inc () {
        Integer integer = 1;
        int unboxing = integer ++;
    }
}

這是一段普通的代碼,但它完整包含了三個(gè)動(dòng)作:將一個(gè)原始數(shù)據(jù)類型轉(zhuǎn)換成包裝類型隧熙、將一個(gè)包裝類型轉(zhuǎn)換成一個(gè)原始數(shù)據(jù)類型片挂、對(duì)一個(gè)包裝類型進(jìn)行直接的運(yùn)算。

接下來通過 javap -verbose 指令解析編譯后的 class 文件,以下是在我的電腦上的 JDK 1.8 版本解析的結(jié)果音念,看看編譯過程發(fā)生了什么事

 public void inc();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=1
         0: iconst_1
         1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         4: astore_1
         5: aload_1
         6: astore_3
         7: aload_1
         8: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        11: iconst_1
        12: iadd
        13: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        16: dup
        17: astore_1
        18: astore        4
        20: aload_3
        21: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        24: istore_2
        25: return

這是節(jié)選自 inc() 方法的一段字節(jié)碼沪饺,查看它的操作指令及注釋,可以得出結(jié)論

  • 自動(dòng)拆箱裝箱是在編譯期完成的
  • javac 將裝箱操作用 Integer.valueOf() 代替了闷愤,將拆箱用 Integer.intValue() 代替了
  • 包裝對(duì)象的運(yùn)算整葡,要先拆箱成原始數(shù)據(jù)類型,進(jìn)行運(yùn)算完畢后再裝箱

通過對(duì)這個(gè)反編譯例子的研究讥脐,我們就明白遭居,在以后編程中,需要進(jìn)行大量計(jì)算的地方旬渠,應(yīng)該使用原始數(shù)據(jù)類型魏滚,在性能敏感的場(chǎng)合,優(yōu)先使用原始數(shù)據(jù)類型坟漱。

關(guān)于 Integer 源碼

整體看一下 Integer 類的源碼鼠次,可以發(fā)現(xiàn)這個(gè)類主要是由幾個(gè)常量,兩個(gè)裝箱拆箱方法芋齿,一些進(jìn)制轉(zhuǎn)換方法腥寇,一些位操作方法組成。

其中有幾個(gè)比較有意思的點(diǎn)觅捆,

  1. 類和常量都被 final 修飾了起來赦役。

很容易由此聯(lián)想到這是一個(gè)不可變類 (immutable),由此想到不可變類的設(shè)計(jì)原則:

  • 類標(biāo)識(shí)打上 final 標(biāo)志
  • 類變量使用 final 修飾
  • 提供構(gòu)造或者工廠方法設(shè)置類變量的初始值栅炒,不提供 set 方法
  • 構(gòu)造時(shí)掂摔,對(duì)引用類型使用深拷貝,避免外部不確定因素的影響
  • 將集合改為不可變集合赢赊,比如使用 Java 9 List.of() 方法
  1. 特別的常量

Integer 類除了提供最大乙漓、最小的常數(shù)外,還提供了位數(shù)和字節(jié)數(shù)的兩個(gè)常量释移。后面這兩個(gè)常量叭披,值得提一下。

如果我們寫過 c 或者 c++ 語言玩讳,就會(huì)知道涩蜘,在這兩種語言中, int 類型在 32 位和 64 位系統(tǒng)中是不確定的熏纯。而在 Java 中同诫,我們無需擔(dān)憂這種不同,因?yàn)樵?Java 語言規(guī)范中明確規(guī)定了各種基本類型的長(zhǎng)度樟澜。

https://docs.oracle.com/javase/specs/jls/se10/html/jls-4.html#jls-4.2

這也是 Java 實(shí)現(xiàn)它的承諾——一次書寫误窖,到處運(yùn)行叮盘,的一個(gè)細(xì)節(jié)。

  1. 關(guān)于緩存

我們都知道 Java 自動(dòng)緩存了一個(gè)小范圍的整數(shù)值贩猎。并且都在無數(shù)的地方中看到了別人對(duì)這個(gè)范圍的描述—— -128~127熊户。但萍膛,如果讀過了 Integer 的源碼吭服,你就會(huì)發(fā)現(xiàn)這是錯(cuò)誤的說法。

        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

在生成緩存的這段代碼中蝗罗,我們注意到了一個(gè)細(xì)節(jié)艇棕,范圍的下限確實(shí)是 -128,但上限是可以調(diào)節(jié)的串塑。
如果我們的程序需要更大的緩存范圍沼琉,我們可以通過在啟動(dòng) JVM 時(shí)增加參數(shù)-XX:AutoBoxCacheMax=N,來設(shè)置這個(gè)緩存范圍的上限桩匪。

對(duì)于緩存打瘪,還可以同樣觀察下其他基本類型的包裝類,都同樣做了一個(gè)小范圍的緩存傻昙。

關(guān)于占用空間

如果我們要?jiǎng)?chuàng)建 10 萬個(gè)整數(shù)闺骚,那么光是對(duì)象頭的占用空間,Integer 就要比 int 多出一個(gè)數(shù)量級(jí)妆档。這是對(duì)象機(jī)制不可避免帶來的問題僻爽,我們?cè)诰帉懖僮鞔罅繑?shù)據(jù)的代碼時(shí),也應(yīng)該考慮占用空間的問題贾惦。

關(guān)于線程安全

我們都知道胸梆,不可變類是實(shí)現(xiàn)線程安全共享對(duì)象的一種方式。但是原始元素類型的運(yùn)算就不是線程安全的须板。如果多個(gè)線程同時(shí)對(duì)一個(gè) int 對(duì)象做運(yùn)算碰镜,就可能引發(fā)并發(fā)問題。

如果有線程安全計(jì)算的需要习瑰,可以使用 AtomicInteger 這樣的線程安全類進(jìn)行計(jì)算洋措。

但,如果不想涉及到類杰刽,想直接在原始數(shù)據(jù)類型上做并發(fā)操作菠发,也是有辦法的。Java 提供了 AtomicIntegerFieldUpdater 這樣的類進(jìn)行 cas 安全操作贺嫂。下面是使用原始數(shù)據(jù)類型實(shí)現(xiàn)一個(gè)計(jì)數(shù)器的方式滓鸠。

 class CompactCounter {
    private volatile long counter;
    private static final AtomicLongFieldUpdater<CompactCounter> updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class, "counter");
    public void increase() {
        updater.incrementAndGet(this);
    }
}

關(guān)于局限性

Java 走過了這么多年的歷程,這種類型系統(tǒng)的設(shè)計(jì)已經(jīng)是很久前的了第喳,現(xiàn)在也逐漸暴露了一些缺點(diǎn)糜俗。

  • 原始數(shù)據(jù)類型不能和泛型完美配合。

Java 的泛型機(jī)制,是一種偽泛型悠抹,它在編譯期將類型轉(zhuǎn)換為特定的類型珠月。這就要求相應(yīng)類型可以轉(zhuǎn)換為 Object。

  • 無法高效地表達(dá)數(shù)據(jù)

如果我們寫過 python楔敌、scala啤挎。就會(huì)知道,這些語言中有 vector卵凑、tuple庆聘。極大地方便了我們編程,簡(jiǎn)潔了代碼勺卢。

但是由于 Java 語言的實(shí)現(xiàn)機(jī)制伙判,在對(duì)象數(shù)組中存放的是對(duì)象的引用,實(shí)際對(duì)象分散在堆內(nèi)存的各個(gè)地方黑忱。這種方式雖然帶來了極大的靈活性宴抚,但是卻帶來了數(shù)據(jù)操作的低效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末甫煞,一起剝皮案震驚了整個(gè)濱河市菇曲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌危虱,老刑警劉巖羊娃,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異埃跷,居然都是意外死亡蕊玷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門弥雹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垃帅,“玉大人,你說我怎么就攤上這事剪勿∶吵希” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵厕吉,是天一觀的道長(zhǎng)酱固。 經(jīng)常有香客問我,道長(zhǎng)头朱,這世上最難降的妖魔是什么运悲? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮项钮,結(jié)果婚禮上班眯,老公的妹妹穿的比我還像新娘希停。我一直安慰自己,他們只是感情好署隘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布宠能。 她就那樣靜靜地躺著,像睡著了一般磁餐。 火紅的嫁衣襯著肌膚如雪违崇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天崖媚,我揣著相機(jī)與錄音亦歉,去河邊找鬼恤浪。 笑死畅哑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的水由。 我是一名探鬼主播荠呐,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼砂客!你這毒婦竟也來了泥张?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤鞠值,失蹤者是張志新(化名)和其女友劉穎媚创,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彤恶,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钞钙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了声离。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芒炼。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖术徊,靈堂內(nèi)的尸體忽然破棺而出本刽,到底是詐尸還是另有隱情,我是刑警寧澤赠涮,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布子寓,位于F島的核電站,受9級(jí)特大地震影響笋除,放射性物質(zhì)發(fā)生泄漏斜友。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一株憾、第九天 我趴在偏房一處隱蔽的房頂上張望蝙寨。 院中可真熱鬧晒衩,春花似錦、人聲如沸墙歪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虹菲。三九已至靠胜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毕源,已是汗流浹背浪漠。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霎褐,地道東北人址愿。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像冻璃,于是被迫代替她去往敵國和親响谓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 廢話不多說省艳,自己進(jìn)入今天的主題 1娘纷、面向?qū)ο蟮奶卣饔心男┓矫妫?答:面向?qū)ο蟮奶卣髦饕幸韵聨讉€(gè)方面: - 抽象:...
    傳奇內(nèi)服號(hào)閱讀 2,341評(píng)論 1 31
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法跋炕,內(nèi)部類的語法赖晶,繼承相關(guān)的語法,異常的語法辐烂,線程的語...
    子非魚_t_閱讀 31,597評(píng)論 18 399
  • 很多朋友在投資基金時(shí)都會(huì)碰到一件事--基金分紅遏插,有些投資者對(duì)此喜聞樂見,而有些人卻認(rèn)為沒有意義棉圈,只是左手倒右手涩堤。那...
    子銘閱讀 243評(píng)論 0 0