Java內存分配,基本數據類型及自動拆裝箱

一 Java內存分配

一般Java在內存分配時會涉及到以下區(qū)域:

  1. 寄存器:我們在程序中無法控制
  2. 棧:存放 基本數據類型的數據和 對象的引用 赡鲜,但對象本身不存放在棧中睬塌,而是存放在堆中
  3. 堆:存放new產生的數據
  4. 靜態(tài)域:存放在對象中用static定義的靜態(tài)成員
  5. 常量池: 存放常量
  6. 非RAM存儲:硬盤永久存儲空間

堆內存用來存放由new創(chuàng)建的對象和數組。在堆中分配的內存昆庇,有Java虛擬機的垃圾回收器管理末贾。

在堆中產生了一個數組或對象后,還可以 在棧中定義一個特殊的變量凰锡,讓棧中這個變量的取值等于數組或對象在堆內存中的首地址未舟,棧中的這個變量就成了數組或對象的引用變量。
引用變量是普通的變量掂为,定義時在棧中分配裕膀,引用變量在程序運行到其作用域之外后被釋放。而數組和對象本身在堆中分配勇哗,即使程序 運行到使用 new 產生數組或者對象的語句所在的代碼塊之外昼扛,數組和對象本身占據的內存不會被釋放,數組和對象在沒有引用變量指向它的時候欲诺,才變?yōu)槔常荒茉诒皇褂茫?然占據內存空間不放扰法,在隨后的一個不確定的時間被垃圾回收器收走(釋放掉)蛹含。

函數中定義的一些基本類型的數據變量對象的引用變量都在函數的棧內存中分配。
棧的優(yōu)勢是存取速度比堆要快塞颁,僅次于直接位于CPU 的寄存器浦箱,而且數據可以共享。但是存在棧中的數據大小與生存周期必須是確定的祠锣。
當在一段代碼塊定義一個變量時酷窥,Java就在棧中 為這個變量分配內存空間,當該變量退出該作用域后伴网,Java會自動釋放掉為該變量所分配的內存空間蓬推,該內存空間可以立即被另作他用。

二 基本數據類型

  1. 基本數據類型(原始數據類型): byte short int long float double char boolean
    基本類型的變量持有原始值澡腾。

    這種類型是通過諸如 int a=7; 的形式來定義的沸伏,稱為自動變量。這里自動變量是字面值动分。不是類的實例毅糟,即不是類的引用,這里并沒有類的存在刺啦。a 是指向一個 int 類型的引用,指向 7 這個字面值纠脾。由于其大小確定生存期可知(這些定義在某個程序塊中玛瘸,程序塊退出后蜕青,字段值就消失),因此存在中.
    由于棧的數據可以共享糊渊,因此 int a=3; int b=3; 這段代碼右核,編譯器首先處理 int a =3; ,先會在棧中創(chuàng)建一個變量為 a 的引用,然后查找有沒有字面值為 3的地址渺绒,沒有找到贺喝,就開辟一個存放 3 這個字面值的地址,然后將a 指向 3 的地址宗兼。接下來處理int b =3; 在創(chuàng)建完 b 這個引用變量后躏鱼,由于棧中已經有 3 這個字面值,便將 b 指向 3 的地址殷绍∪究粒【定義變量,給變量賦值】

    實際上主到,Java中還存在另外一種基本類型void茶行,它也有對應的包裝類 java.lang.Void,不過我們無法直接對它們進行操作登钥。

三 包裝類數據

1. 為什么需要包裝類

Java中的基本類型不是面向對象的畔师,它們只是純粹的數據,除了數值本身的信息之外牧牢,基本類型數據不帶有其他信息或者可操作方法看锉。這在實際使用中存在很多不足,比如结执,在集合類中度陆,我們是無法將int 、double等類型放進去的献幔。因為集合的容器要求元素是Object類型懂傀。為了解決這個不足,對每個基本類型都對應了一個引用的類型蜡感,稱為裝箱基本類型蹬蚁。

為了讓基本類型也具有對象的特征,就出現了包裝類型郑兴,它相當于將基本類型“包裝起來”犀斋,使得它具有了對象的性質,并且為其添加了屬性和方法情连,豐富了基本類型的操作叽粹。

2. 拆箱與裝箱

把基本數據類型轉換成包裝類的過程就是打包裝,英文對應于boxing,中文翻譯為裝箱虫几。

反之锤灿,把包裝類轉換成基本數據類型的過程就是拆包裝,英文對應于unboxing辆脸,中文翻譯為拆箱但校。

在Java SE5之前,可以通過以下代碼進行裝箱:

Integer i = new Integer(6);

3. 自動拆箱與自動裝箱

自動裝箱: 將基本數據類型自動轉換成對應的包裝類啡氢。

自動拆箱:將包裝類自動轉換成對應的基本數據類型状囱。

Integer i =6;  //自動裝箱
int j= i;     //自動拆箱

4. 自動裝箱與自動拆箱的實現原理

自動拆裝箱的代碼:

public static  void main(String[]args){
    Integer integer=1; //裝箱
    int i=integer; //拆箱
}

反編譯以上代碼后可以得到以下代碼:

public static  void main(String[]args){
    Integer integer=Integer.valueOf(1); 
    int i=integer.intValue(); 
}

從反編譯后的代碼可以看出,int的自動裝箱都是通過Integer.valueOf()方法來實現的倘是,Integer的自動拆箱都是通過integer.intValue來實現的亭枷。如果讀者感興趣,可以試著將八種類型都反編譯一遍 辨绊,你會發(fā)現以下規(guī)律:

自動裝箱都是通過包裝類的valueOf()方法來實現的奶栖。自動拆箱都是通過包裝類對象的xxxValue()來實現的。

5. 整型的取值范圍

Java中的整型主要包含byte门坷、short宣鄙、intlong這四種,表示的數字范圍也是從小到大的默蚌,之所以表示范圍不同主要和他們存儲數據時所占的字節(jié)數有關冻晤。

整型的這幾個類型中,

  • byte:byte用1個字節(jié)來存儲绸吸,范圍為-128(-27)到127(27-1)鼻弧,在變量初始化的時候,byte類型的默認值為0锦茁。
  • short:short用2個字節(jié)存儲攘轩,范圍為-32,768 (-2^15)到32,767 (2^15-1),在變量初始化的時候码俩,short類型的默認值為0度帮,一般情況下,因為Java本身轉型的原因稿存,可以直接寫為0笨篷。
  • int:int用4個字節(jié)存儲,范圍為-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1)瓣履,在變量初始化的時候率翅,int類型的默認值為0。
  • long:long用8個字節(jié)存儲袖迎,范圍為-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1)冕臭,在變量初始化的時候腺晾,long類型的默認值為0L或0l,也可直接寫為0辜贵。

6. 超出范圍怎么辦

上面說過了丘喻,整型中,每個類型都有一定的表示范圍念颈,但是,在程序中有些計算會導致超出表示范圍连霉,即溢出榴芳。如以下代碼:

    int i = Integer.MAX_VALUE;
    int j = Integer.MAX_VALUE;

    int k = i + j;
    System.out.println(k); //輸出: -2

這就是發(fā)生了溢出,溢出的時候并不會拋異常跺撼,也沒有任何提示窟感。所以,在程序中歉井,使用同類型的數據進行運算的時候柿祈,一定要注意數據溢出的問題。

7. 緩存

Java SE的自動拆裝箱提供了一個和緩存有關的功能哩至,看如下代碼:

public static void main(String... strings) {

    Integer integer1 = 3;
    Integer integer2 = 3;

    if (integer1 == integer2)
        System.out.println("integer1 == integer2");
    else
        System.out.println("integer1 != integer2");

    Integer integer3 = 300;
    Integer integer4 = 300;

    if (integer3 == integer4)
        System.out.println("integer3 == integer4");
    else
        System.out.println("integer3 != integer4");

}

我們普遍認為上面的兩個判斷的結果都是false躏嚎。雖然比較的值是相等的,但是由于比較的是對象菩貌,而對象的引用不一樣卢佣,所以會認為兩個if判斷都是false的。在Java中箭阶,==比較的是對象引用虚茶,而equals比較的是值。所以仇参,在這個例子中嘹叫,不同的對象有不同的引用,所以在進行比較的時候都將返回false诈乒。奇怪的是罩扇,這里兩個類似的if條件判斷返回不同的布爾值。

上面這段代碼真正的輸出結果:

integer1 == integer2
integer3 != integer4

原因就和Integer中的緩存機制有關抓谴。在Java 5中暮蹂,在Integer的操作上引入了一個新功能來節(jié)省內存和提高性能。整型對象通過使用相同的對象引用實現了緩存和重用癌压。

適用于整數值區(qū)間-128 至 +127仰泻。

只適用于自動裝箱。使用構造函數創(chuàng)建對象不適用滩届。

緩存其實是由valueOf方法實現的集侯,該方法有可能通過緩存經常請求的值而顯著提高空間和時間性能 被啼。

查看Integer的valueOf方法:

public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        //static final int low = -128;
        //當-128=<i<=127的時候,就直接在緩存中取出 i de  Integer 類型對象
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        //否則就在堆內存中創(chuàng)建
        return new Integer(i);
    }

看出對于范圍 [-128,127] 的整數棠枉,valueOf方法做了特殊處理浓体。采用IntegerCache.cache[i + (-IntegerCache.low)]; 這個方法。

查看 IntegerCache類的實現為:

 private static class IntegerCache {
        static final int low = -128; //最小值是固定的
        static final int high;
        static final Integer cache[];//cache 緩存是一個存放Integer類型的數組

        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) {
                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);
            }
            high = h;

            cache = new Integer[(high - low) + 1];  //初始化數組
            int j = low;
            //緩存區(qū)間數據
            for(int k = 0; k < cache.length; k++)
            //將-128~127包裝成256個對象存入緩存
                cache[k] = new Integer(j++);
        }

        private IntegerCache() {}

IntegerCache初始化后內存中就有Integer緩沖區(qū)cache[]了命浴,-128~127區(qū)間的int值有其對應的的包裝對象。這就是 valueOf方法真正的優(yōu)化方法贱除。

public class ZhuangXaing {

    public static void main(String[] args) {
        Integer i= new Integer(12);
        Integer j=12;
        Integer k=Integer.valueOf(12);


        Integer l= new Integer(232);
        Integer m=232;
        Integer n=232;

        Double  q = 232.0;

        System.out.println("use ==.......");    
        System.out.println(i==12);
        System.out.println(i==j);
        System.out.println(j==k);

        System.out.println(l==232);
        System.out.println(l==m);
        System.out.println(m==n);

        System.out.println("use equals.....");
        System.out.println(m.equals(n));
        System.out.println(m.equals(q));

    }

}

輸出結果:

use ==.......
true
false
true
true
false
false
use equals.....
true
false

Integer i= new Integer(12); 是指明了在堆內存中創(chuàng)建對象生闲;
Integer j=12; 是自動裝箱,調用valueOf 方法月幌,返回return IntegerCache.cache[12 + 128]碍讯, 得到的是Integer 緩沖池中的對象。Integer k=Integer.valueOf(12);Integer j=12; 本質上相同扯躺,指向緩沖池中同一對象捉兴。包裝對象與數值比較,自動拆箱录语。
而對于大于127 的數值倍啥,執(zhí)行的都是return new Integer(i) 都在堆內存中,但是地址不同澎埠。

對于equals 方法比較的是數值大卸涸浴:

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

可以看出比較的 obj如果是Integer的實例,則比較拆箱后數值的是否相等失暂。否則返回false彼宠。

下面這段代碼輸出結果是什么:

public class Main {
    public static void main(String[] args) {

        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;

        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
//false
//false

因為Double類的valueOf方法會采用與Integer類的valueOf方法不同的實現。很簡單:在某個范圍內的整型數值的個數是有限的弟塞,而浮點數卻不是凭峡。

其他的包裝器:
Boolean: (全部緩存)
Byte: (全部緩存)

Character ( <=127 緩存)
Short (-128~127 緩存)
Long (-128~127 緩存)

Float (沒有緩存)
Doulbe (沒有緩存)

下面這段代碼輸出結果是什么:

public class Main {
    public static void main(String[] args) {

        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;

        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

先看Boolean類的源碼 ,valueOf方法的實現:

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

而其中的 TRUEFALSE又是什么呢决记?在Boolean中定義了2個靜態(tài)成員屬性:

public static final Boolean TRUE = new Boolean(true);

    /** 
     * The <code>Boolean</code> object corresponding to the primitive 
     * value <code>false</code>. 
     */
    public static final Boolean FALSE = new Boolean(false);

由此可知上面代碼輸出都為true 摧冀。

8. 自動拆箱導致空指針異常

Map<String,Boolean> map =  new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : false);

執(zhí)行該代碼,會報NullPointerException系宫。

Exception in thread "main" java.lang.NullPointerException

既然報了空指針索昂,那么一定是有些地方調用了一個null的對象的某些方法。在這短短的兩行代碼中扩借,看上去只有一處方法調用map.get("test")椒惨,但是我們也都是知道,map已經事先初始化過了潮罪,不會是Null康谆,那么到底是哪里有空指針呢领斥。反編譯上面代碼:

HashMap hashmap = new HashMap();
Boolean boolean1 = Boolean.valueOf(hashmap == null ? false : ((Boolean)hashmap.get("test")).booleanValue());

可以看出((Boolean)hashmap.get("test")).booleanValue()的執(zhí)行過程中報了空指針:

hashmap.get(“test”)->null;

(Boolean)null->null;

null.booleanValue()->報錯

在三目運算符的語法規(guī)范中,當第二沃暗,第三位操作數分別為基本類型和對象時月洛,其中的對象就會拆箱為基本類型進行操作。

由于使用了三目運算符孽锥,并且第二嚼黔、第三位操作數分別是基本類型和對象。所以對對象進行拆箱操作惜辑,由于該對象為null隔崎,所以在拆箱過程中調用null.booleanValue()的時候就報了NullPointerException. 。

正確寫法

Map<String,Boolean> map =  new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);

保證三目運算符的第二第三位操作數都為對象類型即可韵丑。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虚缎,隨后出現的幾起案子撵彻,更是在濱河造成了極大的恐慌,老刑警劉巖实牡,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陌僵,死亡現場離奇詭異,居然都是意外死亡创坞,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巫俺,“玉大人筑辨,你說我怎么就攤上這事「俣拢” “怎么了巡雨?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長席函。 經常有香客問我铐望,道長,這世上最難降的妖魔是什么茂附? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任正蛙,我火速辦了婚禮,結果婚禮上营曼,老公的妹妹穿的比我還像新娘乒验。我一直安慰自己,他們只是感情好蒂阱,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布徊件。 她就那樣靜靜地躺著奸攻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虱痕。 梳的紋絲不亂的頭發(fā)上睹耐,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音部翘,去河邊找鬼硝训。 笑死,一個胖子當著我的面吹牛新思,可吹牛的內容都是我干的窖梁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼夹囚,長吁一口氣:“原來是場噩夢啊……” “哼纵刘!你這毒婦竟也來了?” 一聲冷哼從身側響起荸哟,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤假哎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鞍历,有當地人在樹林里發(fā)現了一具尸體舵抹,經...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年劣砍,在試婚紗的時候發(fā)現自己被綠了惧蛹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刑枝,死狀恐怖香嗓,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情装畅,我是刑警寧澤陶缺,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站洁灵,受9級特大地震影響饱岸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜徽千,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一苫费、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧双抽,春花似錦百框、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柬泽。三九已至,卻和暖如春嫁蛇,著一層夾襖步出監(jiān)牢的瞬間锨并,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工睬棚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留第煮,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓抑党,卻偏偏與公主長得像包警,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子底靠,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內容