天天看點

吃人的那些 Java 名詞:對象、引用、堆、棧(2)

二、堆、棧、堆棧

誰來告訴我,為什麼有很多地方(書、部落格等等)把棧叫做堆棧,把堆棧叫做棧?搞得我都頭暈目眩了——繞着門柱估計轉了 80 圈,不暈才怪!

我查了一下金山詞霸,結果如下:

吃人的那些 Java 名詞:對象、引用、堆、棧(2)

我的天呐,更暈了,有沒有!怎麼才能不暈呢?我這裡有幾招武功秘籍,你們盡管拿去一睹為快:

1)以後再看到堆、棧、堆棧三個在一起打牌的時候,直接把“堆棧”踢出去;這仨人不适合在一起玩,因為堆和棧才是老相好;你“堆棧”來這插一腳算怎麼回事;這世界上隻存在“堆、棧”或者“堆棧”(标點符号很重要哦)。

2)堆是在程式運作時在記憶體中申請的空間(可了解為動态的過程);切記,不是在編譯時;是以,Java 中的對象就放在這裡,這樣做的好處就是:

當需要一個對象時,隻需要通過 new 關鍵字寫一行代碼即可,當執行這行代碼時,會自動在記憶體的“堆”區配置設定空間——這樣就很靈活。

另外,需要記住,堆遵循“先進後出”的規則(此處有雷)。就好像,一個和尚去挑了一擔水,然後把一擔水裝缸裡面,等到他口渴的時候他再用瓢舀出來喝。請放肆地打開你的腦洞腦補一下這個流程:缸底的水是先進去的,但後出來的。是以,我建議這位和尚在缸上貼個标簽——保存期限 90 天,過期飲用,後果自負!

還是記不住,看下圖:

吃人的那些 Java 名詞:對象、引用、堆、棧(2)

(不好意思,這是鼎,不是缸,将就一下哈)

3)棧,又名堆棧(簡直了,完全不符合程式員的思維啊,我們程式員習慣說一就是一,說二就是二嘛),能夠和處理器(CPU,也就是腦子)直接關聯,是以通路速度更快;舉個十分不恰當的例子哈——眼睛相對嘴巴是離腦子近的一方,是以,你可以一目十行,但絕對做不到一開口就讀十行字,哪怕十個字也做不到。

既然通路速度快,要好好利用啊!Java 就把對象的引用放在棧裡。為什麼呢?因為引用的使用頻率高嗎?

不是的,因為 Java 在編譯程式時,必須明确的知道存儲在棧裡的東西的生命周期,否則就沒法釋放舊的記憶體來開辟新的記憶體空間存放引用——空間就那麼大,前浪要把後浪拍死在沙灘上啊。

現在清楚堆、棧和堆棧了吧?

三、基本資料類型

先來看《Java 程式設計思想》中的一段話:

在程式設計中經常用到一系列類型,他們需要特殊對待。之是以特殊對待,是因為 new 将對象存儲于“堆”中,故用 new 建立一個對象──特别小、簡單的變量,往往不是很有效。是以,不用new來建立這類變量,而是建立一個并非是引用的變量,這個變量直接存儲值,并置于棧中,是以更加高效。

在 Java 中,這些基本類型有:boolean、char、byte、short、int、long、float、double 和 void;還有與之對應的包裝器:Boolean、Character、Byte、Short、Integer、Long、Float、Double 和 Void;它們之間涉及到裝箱和拆箱,點選連結。

看兩行簡單的代碼:

int a = 3;

int b = 3;

這兩行代碼在編譯的時候是什麼樣子呢?

編譯器當然是先處理 int a = 3;,不然還能跳過嗎?編譯器在處理 int a = 3; 時在棧中建立了一個變量為 a 的記憶體空間,然後查找有沒有字面值為 3 的位址,沒找到,就開辟一個存放 3 這個字面值的位址,然後将 a 指向 3 的位址。

編譯器忙完了 int a = 3;,就來接着處理 int b = 3;;在建立完 b 的變量後,由于棧中已經有 3 這個字面值,就将 b 直接指向 3 的位址;就不需要再開辟新的空間了。

依據上面的概述,我們假設在定義完 a 與 b 的值後,再令 a=4,此時 b 是等于 3 呢,還是 4 呢?

思考一下,再看答案哈。

答案揭曉:當編譯器遇到 a = 4;時,它會重新搜尋棧中是否有 4 的字面值,如果沒有,重新開辟位址存放 4 的值;如果已經有了,則直接将 a 指向 4 這個位址;是以 a 值的改變不會影響到 b 的值哦。

最後,留個作業吧,下面這段代碼在運作時會輸出什麼呢?

public class Test1 {
    public static void main(String args[]) {
        int a = 1;
        int b = 1;

        a = 2;

        System.out.println(a);
        System.out.println(b);

        TT t = new TT("T");
        TT t1 = t;
        t.setName("TT");


        System.out.println(t.getName());
        System.out.println(t1.getName());
    }
}

class TT{
    private String name;

    public TT (String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name1) {
        this.name = name1;
    }
}