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

做爲一個有着 8 年 Java 編程經驗的 IT 老兵,提及來很慚愧,我被 Java 當中的四五個名詞一直困擾着:對象、引用、堆、棧、堆棧(棧可同堆棧,所以是四個名詞,也是五個名詞)。每次我看到這幾個名詞,都隱隱約約以爲本身在被一隻無形的大口慢慢地吞噬,只剩下滿地的衣服碎屑(爲何不是骨頭,由於骨頭也好吃)。html

記得中學的課本上,有一篇名爲《狂人日記》課文;那時候根本理解不了魯迅寫這篇文章要表達的中心思想,只以爲滿篇的「吃人」使人心情壓抑;老師在講臺上慷慨激昂的講,大多數的同窗同我同樣,在課本面前「癡癡」的發呆。java

十幾年後,再讀《狂人日記》,恍然如夢:程序員

魯迅先生以狂人的口吻,再現了動亂時期下中國人的精神狀態,視角新穎,文筆細膩又不乏辛辣之味。編程

當時的中國,混亂成了主色調。以清廷和孔教爲主的封建舊思想還在潛移默化地影響着人們的思想,與此同時以革命和新思潮爲主的現代思想已經開始了對大衆靈魂的洗滌和衝擊。微信

最近,和沉默王二技術交流羣(120926808)的羣友們交流後,Java 中那四五個會吃人的名詞:對象、引用、堆、棧、堆棧,彷佛在腦海中也清晰了起來,儘管疑惑有時候仍然會在陰雲密佈時跑出來——正鑑於此,這篇文章剛好作一下概括。oop

1、對象和引用

在 Java 中,儘管一切均可以看作是對象,但計算機操做的並不是對象自己,而是對象的引用。 這話乍眼一看,似懂非懂。究竟什麼是對象,什麼又是引用呢?this

先來看對象的定義:按照通俗的說法,每一個對象都是某個類(class)的一個實例(instance)。那麼,實例化的過程怎麼描述呢?來看代碼(類是 String):spa

new String("我是對象張三");
new String("我是對象李四");
複製代碼

在 Java 中,實例化指的就是經過關鍵字「new」來建立對象的過程。以上代碼在運行時就會建立兩個對象——"我是對象張三"和"我是對象李四";如今,該怎麼操做他們呢?設計

去過公園的同窗應該會見過幾個大爺,他們頗有一番本領——個個都能把風箏飛得老高老高,徒留咱們眼饞的份!風箏飛那麼高,沒辦法直接用手拽着飛啊,全要靠一根長長的看不見的結實的繩子來牽引!操做 Java 對象也是這個理,得有一根繩——也就是接下來要介紹的「引用」(咱們肉眼也經常看不見它)。code

String zhangsan, lisi;
zhangsan = new String("我是對象張三");
lisi = new String("我是對象李四");
複製代碼

這三行代碼該怎麼理解呢?

先來看第一行代碼:String zhangsan, lisi;——聲明瞭兩個變量 zhangsan 和 lisi,他們的類型爲 String。

①、==歧義==:zhangsan 和 lisi 此時被稱爲引用。

你也許聽過這樣一句古文:「神之於形,猶利之於刀;未聞刀沒而利存,豈容形亡而神在?」這是無神論者範縝(zhen)的名言,大體的意思就是:靈魂對於肉體來講,就像刀刃對於刀身;從沒據說過刀身都沒了刀刃還存在,那麼怎麼可能容許肉體死亡了而靈魂還在呢?

「引用」之於對象,就比如刀刃之於刀身,對象尚未建立,又怎麼存在對象的「引用」呢?

若是 zhangsan 和 lisi 此時不能被稱爲「引用」,那麼他們是什麼呢?答案很簡單,就是變量啊!(鄙人理解)

②、==誤解==:zhangsan 和 lisi 此時的默認值爲 null

應該說 zhangsan 和 lisi 此時的值爲 undefined——借用 JavaScript 的關鍵字;也就是未定義;或者應該是一個新的關鍵字 uninitialized——未初始化。但不論是 undefined 仍是 uninitialized,都與 null 不一樣。

既然沒有初始化,zhangsan 和 lisi 此時就不能被使用。假如強行使用的話,編譯器就會報錯,提醒 zhangsan 和 lisi 尚未出生(初始化);見下圖。

若是把 zhangsan 和 lisi 初始化爲 null,編譯器是承認的(見下圖);因而可知,zhangsan 和 lisi 此時的默認值不爲 null

再來看第二行代碼:zhangsan = new String("我是對象張三");——建立「我是對象張三"的 String 類對象,並將其賦值給 zhangsan 這個變量。

此時,zhangsan 就是"我是對象張三"的引用;「=」操做符賦予了 zhangsan 這樣神聖的權利。

第三行代碼 lisi = new String("我是對象李四");和第二行代碼 zhangsan = new String("我是對象張三");同理。

如今,我能夠下這樣一個結論了——對象是經過 new 關鍵字建立的;引用是依賴於對象的;= 操做符把對象賦值給了引用

咱們再來看這樣一段代碼:

String zhangsan, lisi;
zhangsan = new String("我是對象張三");
lisi = new String("我是對象李四");
zhangsan = lisi;
複製代碼

zhangsan = lisi; 執行事後,zhangsan 就再也不是"我是對象張三"的引用了;zhangsan 和 lisi 指向了同一個對象("我是對象李四");所以,你知道 System.out.println(zhangsan == lisi); 打印的是 false 仍是 true 了嗎?

2、堆、棧、堆棧

誰來告訴我,爲何有不少地方(書、博客等等)把棧叫作堆棧,把堆棧叫作棧?搞得我都頭暈目眩了——繞着門柱估計轉了 80 圈,不暈纔怪!

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

個人天吶,更暈了,有沒有!怎麼才能不暈呢?我這裏有幾招武功祕籍,大家儘管拿去一睹爲快:

1)之後再看到堆、棧、堆棧三個在一塊兒打牌的時候,直接把「堆棧」踢出去;這仨人不適合在一塊兒玩,由於堆和棧纔是老相好;你「堆棧」來這插一腳算怎麼回事;這世界上只存在「堆、棧」或者「堆棧」(標點符號很重要哦)。

2)堆是在程序運行時在內存中申請的空間(可理解爲動態的過程);切記,不是在編譯時;所以,Java 中的對象就放在這裏,這樣作的好處就是:

當須要一個對象時,只須要經過 new 關鍵字寫一行代碼便可,當執行這行代碼時,會自動在內存的「堆」區分配空間——這樣就很靈活。

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

仍是記不住,看下圖:

(很差意思,這是鼎,不是缸,將就一下哈)

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

既然訪問速度快,要好好利用啊!Java 就把對象的引用放在棧裏。爲何呢?由於引用的使用頻率高嗎?

不是的,由於 Java 在編譯程序時,必須明確的知道存儲在棧裏的東西的生命週期,不然就無法釋放舊的內存來開闢新的內存空間存放引用——空間就那麼大,前浪要把後浪拍死在沙灘上啊。

如今清楚堆、棧和堆棧了吧?

3、基本數據類型

先來看《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;
    }
}
複製代碼

上一篇:如何理解 Java 中的繼承?

下一篇:Java 的操做符——「=」號

若是你以爲文章對你有所幫助,也蠻有趣的,就微信搜索「沉默王二」關注一下個人公衆號。噓,回覆關鍵字「Java」更有好禮相送哦。

相關文章
相關標籤/搜索