作為一個有著 8 年 Java 編程經(jīng)驗(yàn)的 IT 老兵悬荣,說起來很慚愧,我被 Java 當(dāng)中的四五個名詞一直困擾著:對象疙剑、引用氯迂、堆、棧言缤、堆棧(椊朗矗可同堆棧,因此是四個名詞管挟,也是五個名詞)轿曙。每次我看到這幾個名詞,都隱隱約約覺得自己在被一只無形的大口慢慢地吞噬僻孝,只剩下滿地的衣服碎屑(為什么不是骨頭拳芙,因?yàn)楣穷^也好吃)。
記得中學(xué)的課本上皮璧,有一篇名為《狂人日記》課文;那時候根本理解不了魯迅寫這篇文章要表達(dá)的中心思想分飞,只覺得滿篇的“吃人”令人心情壓抑悴务;老師在講臺上慷慨激昂的講,大多數(shù)的同學(xué)同我一樣,在課本面前“癡癡”的發(fā)呆讯檐。
十幾年后羡疗,再讀《狂人日記》,恍然如夢:
魯迅先生以狂人的口吻别洪,再現(xiàn)了動亂時期下中國人的精神狀態(tài)叨恨,視角新穎,文筆細(xì)膩又不乏辛辣之味挖垛。
當(dāng)時的中國痒钝,混亂成了主色調(diào)。以清廷和孔教為主的封建舊思想還在潛移默化地影響著人們的思想痢毒,與此同時以革命和新思潮為主的現(xiàn)代思想已經(jīng)開始了對大眾靈魂的洗滌和沖擊送矩。
最近,和沉默王二技術(shù)交流群(120926808)的群友們交流后哪替,Java 中那四五個會吃人的名詞:對象栋荸、引用、堆凭舶、棧晌块、堆棧,似乎在腦海中也清晰了起來帅霜,盡管疑惑有時候仍然會在陰云密布時跑出來——正鑒于此匆背,這篇文章恰好做一下歸納。
一义屏、對象和引用
在 Java 中靠汁,盡管一切都可以看做是對象,但計算機(jī)操作的并非對象本身闽铐,而是對象的引用蝶怔。 這話乍眼一看,似懂非懂兄墅。究竟什么是對象踢星,什么又是引用呢?
先來看對象的定義:按照通俗的說法隙咸,每個對象都是某個類(class)的一個實(shí)例(instance)沐悦。那么,實(shí)例化的過程怎么描述呢五督?來看代碼(類是 String):
new String("我是對象張三");
new String("我是對象李四");
在 Java 中藏否,實(shí)例化指的就是通過關(guān)鍵字“new”來創(chuàng)建對象的過程。以上代碼在運(yùn)行時就會創(chuàng)建兩個對象——"我是對象張三"和"我是對象李四"充包;現(xiàn)在副签,該怎么操作他們呢遥椿?
去過公園的同學(xué)應(yīng)該會見過幾個大爺,他們很有一番本領(lǐng)——個個都能把風(fēng)箏飛得老高老高淆储,徒留我們眼饞的份冠场!風(fēng)箏飛那么高,沒辦法直接用手拽著飛啊本砰,全要靠一根長長的看不見的結(jié)實(shí)的繩子來牽引碴裙!操作 Java 對象也是這個理,得有一根繩——也就是接下來要介紹的“引用”(我們?nèi)庋垡渤3点额?床灰娝?/p>
String zhangsan, lisi;
zhangsan = new String("我是對象張三");
lisi = new String("我是對象李四");
這三行代碼該怎么理解呢舔株?
先來看第一行代碼:String zhangsan, lisi;
——聲明了兩個變量 zhangsan 和 lisi,他們的類型為 String咖楣。
①督笆、==歧義==:zhangsan 和 lisi 此時被稱為引用。
你也許聽過這樣一句古文:“神之于形诱贿,猶利之于刀娃肿;未聞刀沒而利存,豈容形亡而神在珠十?”這是無神論者范縝(zhen)的名言料扰,大致的意思就是:靈魂對于肉體來說,就像刀刃對于刀身焙蹭;從沒聽說過刀身都沒了刀刃還存在晒杈,那么怎么可能允許肉體死亡了而靈魂還在呢?
“引用”之于對象孔厉,就好比刀刃之于刀身拯钻,對象還沒有創(chuàng)建,又怎么存在對象的“引用”呢撰豺?
如果 zhangsan 和 lisi 此時不能被稱為“引用”粪般,那么他們是什么呢?答案很簡單污桦,就是變量澳洞酢!(鄙人理解)
②凡橱、==誤解==:zhangsan 和 lisi 此時的默認(rèn)值為 null
小作。
應(yīng)該說 zhangsan 和 lisi 此時的值為 undefined
——借用 JavaScript 的關(guān)鍵字;也就是未定義稼钩;或者應(yīng)該是一個新的關(guān)鍵字 uninitialized
——未初始化顾稀。但不管是 undefined
還是 uninitialized
,都與 null
不同坝撑。
既然沒有初始化础拨,zhangsan 和 lisi 此時就不能被使用氮块。假如強(qiáng)行使用的話,編譯器就會報錯诡宗,提醒 zhangsan 和 lisi 還沒有出生(初始化);見下圖击儡。
如果把 zhangsan 和 lisi 初始化為 null
塔沃,編譯器是認(rèn)可的(見下圖);由此可見阳谍,zhangsan 和 lisi 此時的默認(rèn)值不為 null
蛀柴。
再來看第二行代碼:zhangsan = new String("我是對象張三");
——創(chuàng)建“我是對象張三"的 String 類對象,并將其賦值給 zhangsan 這個變量矫夯。
此時鸽疾,zhangsan 就是"我是對象張三"的引用;“=”操作符賦予了 zhangsan 這樣神圣的權(quán)利训貌。
第三行代碼 lisi = new String("我是對象李四");
和第二行代碼 zhangsan = new String("我是對象張三");
同理制肮。
現(xiàn)在,我可以下這樣一個結(jié)論了——對象是通過 new
關(guān)鍵字創(chuàng)建的递沪;引用是依賴于對象的豺鼻;=
操作符把對象賦值給了引用。
我們再來看這樣一段代碼:
String zhangsan, lisi;
zhangsan = new String("我是對象張三");
lisi = new String("我是對象李四");
zhangsan = lisi;
當(dāng) zhangsan = lisi;
執(zhí)行過后款慨,zhangsan 就不再是"我是對象張三"的引用了儒飒;zhangsan 和 lisi 指向了同一個對象("我是對象李四");因此檩奠,你知道 System.out.println(zhangsan == lisi);
打印的是 false
還是 true
了嗎桩了?
二、堆埠戳、棧井誉、堆棧
誰來告訴我,為什么有很多地方(書乞而、博客等等)把棧叫做堆棧送悔,把堆棧叫做棧?搞得我都頭暈?zāi)垦A恕@著門柱估計轉(zhuǎn)了 80 圈爪模,不暈才怪欠啤!
我查了一下金山詞霸,結(jié)果如下:
我的天吶屋灌,更暈了洁段,有沒有!怎么才能不暈?zāi)毓补课疫@里有幾招武功秘籍祠丝,你們盡管拿去一睹為快:
1)以后再看到堆疾呻、棧、堆棧三個在一起打牌的時候写半,直接把“堆棸段希”踢出去;這仨人不適合在一起玩叠蝇,因?yàn)槎押蜅2攀抢舷嗪昧г溃荒恪岸褩!眮磉@插一腳算怎么回事悔捶;這世界上只存在“堆铃慷、棧”或者“堆椡筛茫”(標(biāo)點(diǎn)符號很重要哦)犁柜。
2)堆是在程序運(yùn)行時在內(nèi)存中申請的空間(可理解為動態(tài)的過程);切記堂淡,不是在編譯時馋缅;因此,Java 中的對象就放在這里淤齐,這樣做的好處就是:
當(dāng)需要一個對象時股囊,只需要通過 new 關(guān)鍵字寫一行代碼即可,當(dāng)執(zhí)行這行代碼時更啄,會自動在內(nèi)存的“堆”區(qū)分配空間——這樣就很靈活稚疹。
另外,需要記住祭务,堆遵循“先進(jìn)后出”的規(guī)則(此處有雷)内狗。就好像,一個和尚去挑了一擔(dān)水义锥,然后把一擔(dān)水裝缸里面柳沙,等到他口渴的時候他再用瓢舀出來喝。請放肆地打開你的腦洞腦補(bǔ)一下這個流程:缸底的水是先進(jìn)去的拌倍,但后出來的赂鲤。所以,我建議這位和尚在缸上貼個標(biāo)簽——保質(zhì)期 90 天柱恤,過期飲用数初,后果自負(fù)!
還是記不住梗顺,看下圖:
(不好意思泡孩,這是鼎,不是缸寺谤,將就一下哈)
3)棧仑鸥,又名堆棧(簡直了吮播,完全不符合程序員的思維啊,我們程序員習(xí)慣說一就是一眼俊,說二就是二嘛)意狠,能夠和處理器(CPU,也就是腦子)直接關(guān)聯(lián)疮胖,因此訪問速度更快摄职;舉個十分不恰當(dāng)?shù)睦庸?strong>眼睛相對嘴巴是離腦子近的一方,因此获列,你可以一目十行,但絕對做不到一開口就讀十行字蛔垢,哪怕十個字也做不到击孩。
既然訪問速度快,要好好利用芭羝帷巩梢!Java 就把對象的引用放在棧里。為什么呢艺玲?因?yàn)橐玫氖褂妙l率高嗎括蝠?
不是的,因?yàn)?Java 在編譯程序時饭聚,必須明確的知道存儲在棧里的東西的生命周期忌警,否則就沒法釋放舊的內(nèi)存來開辟新的內(nèi)存空間存放引用——空間就那么大,前浪要把后浪拍死在沙灘上啊秒梳。
現(xiàn)在清楚堆法绵、棧和堆棧了吧?
三酪碘、基本數(shù)據(jù)類型
先來看《Java 編程思想》中的一段話:
在程序設(shè)計中經(jīng)常用到一系列類型朋譬,他們需要特殊對待。之所以特殊對待兴垦,是因?yàn)?new 將對象存儲于“堆”中徙赢,故用 new 創(chuàng)建一個對象──特別小、簡單的變量探越,往往不是很有效狡赐。因此,不用new來創(chuàng)建這類變量扶关,而是創(chuàng)建一個并非是引用的變量阴汇,這個變量直接存儲值,并置于棧中节槐,因此更加高效搀庶。
在 Java 中拐纱,這些基本類型有:boolean、char哥倔、byte秸架、short、int咆蒿、long东抹、float、double 和 void沃测;還有與之對應(yīng)的包裝器:Boolean缭黔、Character、Byte蒂破、Short馏谨、Integer、Long附迷、Float惧互、Double 和 Void;它們之間涉及到裝箱和拆箱喇伯,點(diǎn)擊鏈接喊儡。
看兩行簡單的代碼:
int a = 3;
int b = 3;
這兩行代碼在編譯的時候是什么樣子呢?
編譯器當(dāng)然是先處理 int a = 3;
稻据,不然還能跳過嗎艾猜?編譯器在處理 int a = 3;
時在棧中創(chuàng)建了一個變量為 a 的內(nèi)存空間,然后查找有沒有字面值為 3 的地址攀甚,沒找到箩朴,就開辟一個存放 3 這個字面值的地址,然后將 a 指向 3 的地址秋度。
編譯器忙完了 int a = 3;
炸庞,就來接著處理 int b = 3;
;在創(chuàng)建完 b 的變量后荚斯,由于棧中已經(jīng)有 3 這個字面值埠居,就將 b 直接指向 3 的地址;就不需要再開辟新的空間了事期。
依據(jù)上面的概述滥壕,我們假設(shè)在定義完 a 與 b 的值后,再令 a=4兽泣,此時 b 是等于 3 呢绎橘,還是 4 呢?
思考一下,再看答案哈称鳞。
答案揭曉:當(dāng)編譯器遇到 a = 4;
時涮较,它會重新搜索棧中是否有 4 的字面值,如果沒有冈止,重新開辟地址存放 4 的值狂票;如果已經(jīng)有了,則直接將 a 指向 4 這個地址熙暴;因此 a 值的改變不會影響到 b 的值哦闺属。
最后,留個作業(yè)吧周霉,下面這段代碼在運(yùn)行時會輸出什么呢掂器?
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 的操作符——“=”號
微信搜索「沉默王二」公眾號俱箱,關(guān)注后回復(fù)「免費(fèi)視頻」獲取 500G Java 高質(zhì)量教學(xué)視頻(已分門別類)唉匾。