終于要寫點(diǎn)干貨了挣饥,其實(shí)思考了很久下面一篇文章要寫什么锚赤,主要的糾結(jié)點(diǎn)在于题翻,既想要分享那些精美的知識(shí)宽涌,又怕這些知識(shí)不太好嚼平夜。后來想想還是對(duì)初學(xué)者不太好友算了..一來這系列文章叫做學(xué)習(xí)筆記,我的卸亮。另外寫得足夠有料忽妒,才能發(fā)揮筆記的作用,不然索然無味的兼贸,連收藏段直、喜歡的意義也沒有了。
寫在文章之前
終于寫點(diǎn)干貨了寝受,想先簡(jiǎn)單談?wù)勛约旱囊恍┛捶琅!?duì)于我自己而言,我比較厭煩那些繁瑣的無聊的知識(shí)點(diǎn)很澄,反而更在乎一些實(shí)際應(yīng)用的東西京闰。但了解一些底層的東西是非常有意義的,它有助于我們理解程序甩苛。
每一點(diǎn)知識(shí)的積累蹂楣,終會(huì)有用武之地。也許讯蒲,它會(huì)使您在面試過程中正確地回答一道面試題痊土;也許,它會(huì)讓您更加清楚Java底層的實(shí)現(xiàn)方式墨林;也許赁酝,它能讓您在學(xué)業(yè)上感到更加充實(shí)...(以上摘自梁勇著的Java深入解析_前言)
Java中的數(shù)據(jù)類型
Java是一種強(qiáng)類型的語言犯祠。這意味著必須為每一個(gè)變量都聲明一種類型。
在Java中酌呆,你可以把數(shù)據(jù)類型分為兩部分衡载,一部分是基本類型(primitive type):4種整形、2種浮點(diǎn)類型隙袁、1種用于表示Unicode編碼的字符單元的字符類型char和1種用于表示真值的boolean類型痰娱。
另外一部分是引用類型(reference type),如String和List菩收。每個(gè)基本類型都有一個(gè)對(duì)應(yīng)的引用類型梨睁,稱作裝箱基本類型(boxed primitive)。裝箱基本類中對(duì)應(yīng)于int娜饵、double坡贺、boolean的是Integer、Double和Boolean划咐。
Java中的特例
Java是一種完全面向?qū)ο蟮恼Z言拴念,從理論上來說,在Java中應(yīng)該不存在對(duì)象以外的事務(wù)褐缠,即所有的類型都是對(duì)象政鼠。然而,在Java8中的8種基本數(shù)據(jù)類型不是對(duì)象队魏,之所以這樣設(shè)計(jì)公般,是因?yàn)橄鄬?duì)于對(duì)象來說,基本數(shù)據(jù)在使用上更加方便胡桨,并且在效率上也高于對(duì)象類型官帘。所以這就需要去了解一下Java中創(chuàng)建對(duì)象的過程。
創(chuàng)建對(duì)象的過程
當(dāng)程序運(yùn)行時(shí)昧谊,對(duì)象是怎么進(jìn)行安排放置的呢刽虹?特別是內(nèi)存是怎樣分配的呢?
Java大體上會(huì)把內(nèi)存分為四塊區(qū)域:堆呢诬、棧涌哲、靜態(tài)區(qū)、常量區(qū)尚镰。
- 堆 : 位于RAM中阀圾,用于存放所有的java對(duì)象。
- 棧 : 位于RAM中狗唉,引用就存在于棧中初烘。
- 靜態(tài)區(qū): 位于RAM中,被static修飾符修飾的變量會(huì)被放在這里
- 常量區(qū):位于ROM中, 很明顯肾筐,放常量的哆料。(其實(shí)常量通常直接存放在程序代碼的內(nèi)部,因?yàn)檫@樣非常安全吗铐,因?yàn)樗鼈冇肋h(yuǎn)都不會(huì)被改變)
所以當(dāng)我們創(chuàng)建對(duì)象剧劝,例如實(shí)例化一個(gè)Person類:
Person p = new Person()
首先,會(huì)在堆中開辟一塊空間存放這個(gè)新來的Person對(duì)象抓歼。然后,會(huì)創(chuàng)建一個(gè)引用p拢锹,存放在棧中谣妻,這個(gè)引用p指向Person對(duì)象(事實(shí)上是,p的值就是Person對(duì)象的內(nèi)存地址)卒稳。
這樣蹋半,我們通過訪問p,然后得到了Person的內(nèi)存地址充坑,進(jìn)而找到了Person對(duì)象减江。
然后又有了這樣一句代碼:
Person p2 = p;
這句代碼的含義是:
創(chuàng)建了一個(gè)新的引用p2,保存在棧中捻爷,引用的地址也指向Person的地址辈灼。這個(gè)時(shí)候,你通過p2來改變Person對(duì)象的狀態(tài)也榄,也會(huì)改變p的結(jié)果巡莹。因?yàn)樗鼈冎赶蛲粋€(gè)對(duì)象。(String除外甜紫,之后會(huì)專門講String)
此時(shí)降宅,內(nèi)存中是這樣的:
有一個(gè)很通俗的方式來講解引用和對(duì)象。大家對(duì)于快捷方式應(yīng)該不會(huì)陌生吧囚霸?我們桌面的圖標(biāo)大部分都是快捷方式腰根。它并不是我們安裝在電腦上的應(yīng)用的可執(zhí)行文件(不是.exe文件),那么為什么點(diǎn)擊它可以打開應(yīng)用程序呢拓型?是因?yàn)榭旖莘绞竭B接了文件额嘿,這就像是引用和對(duì)象的關(guān)系了。
我們不直接對(duì)文件進(jìn)行操作吨述,而是通過快捷方式來進(jìn)行操作岩睁。快捷方式不能獨(dú)立存在揣云,同樣捕儒,引用也不能獨(dú)立存在(你可以只創(chuàng)建一個(gè)引用,但是當(dāng)你要使用它的時(shí)候必須得給它賦值,否則它將毫無用處)刘莹。
一個(gè)文件可以有多個(gè)快捷方式阎毅,同樣一個(gè)對(duì)象也可以有多個(gè)引用。而一個(gè)引用只能同時(shí)對(duì)應(yīng)一個(gè)對(duì)象点弯。
在java里扇调,“=”不能被看成是一個(gè)賦值語句,它不是在把一個(gè)對(duì)象賦給另外一個(gè)對(duì)象抢肛,它的執(zhí)行過程實(shí)質(zhì)上是將右邊對(duì)象的地址傳給了左邊的引用狼钮,使得左邊的引用指向了右邊的對(duì)象。java表面上看起來沒有指針捡絮,但它的引用其實(shí)質(zhì)就是一個(gè)指針熬芜。在java里,“=”語句不應(yīng)該被翻譯成賦值語句福稳,因?yàn)樗鶊?zhí)行的確實(shí)不是一個(gè)簡(jiǎn)單的賦值過程涎拉,而是一個(gè)傳地址的過程,被譯成賦值語句會(huì)造成很多誤解的圆,譯得不準(zhǔn)確鼓拧。
特例:基本數(shù)據(jù)類型
為什么要有特例呢?是因?yàn)閚ew將對(duì)象存儲(chǔ)在“堆”里越妈,一是用new創(chuàng)建一個(gè)對(duì)象——特別是小的季俩,簡(jiǎn)單的變量(Java中數(shù)據(jù)定長,為了可移植性)往往不是很明智而且有效的方法叮称,二是因?yàn)椤岸选笨臻g本來就有限种玛,如果頻繁的操作會(huì)導(dǎo)致不可想象的錯(cuò)誤,并且別忘了第一篇文章里面提到的瓤檐,Java的設(shè)計(jì)初衷是什么赂韵。
所以針對(duì)這些類型,Java采取了與C和C++相同的方法挠蛉,也就是說祭示,不用new來創(chuàng)建變量,二是創(chuàng)建一個(gè)并非是引用的“自動(dòng)”變量谴古。這個(gè)變量直接存儲(chǔ)“值”并置于常量區(qū)中质涛,因此更加高效。
先來看一個(gè)例子:
int i = 2;
int j = 2;
我們需要知道的是掰担,在常量區(qū)中汇陆,相同的常量只會(huì)存在一個(gè)。當(dāng)執(zhí)行第一句代碼時(shí)带饱。先查找常量區(qū)中有沒有2毡代,沒有阅羹,則開辟一個(gè)空間存放2,然后在棧中存入一個(gè)變量i教寂,讓i指向2捏鱼;
執(zhí)行第二句的時(shí)候,查找發(fā)現(xiàn)2已經(jīng)存在了酪耕,所以就不開辟新空間了导梆。直接在棧中保存一個(gè)新變量j,讓j指向2迂烁;
當(dāng)然看尼,java堆每一個(gè)基本數(shù)據(jù)類型都提供了對(duì)應(yīng)的包裝類。我們依舊可以用new操作符來創(chuàng)建我們想要的變量盟步。
Integer i = new Integer(1);
Integer j = new Integer(1);
但是狡忙,用new操作符創(chuàng)建的對(duì)象是不同的,也就是說址芯,此時(shí),i和j指向不同的內(nèi)存地址窜觉。因?yàn)槊看握{(diào)用new操作符谷炸,都會(huì)在堆開辟新的空間。
深入了解Integer
來看一個(gè)例子:
第一個(gè)返回true很好理解禀挫,就像上面講的旬陡,a和b指向相同的地址。
第二個(gè)返回false是為什么呢语婴?下面細(xì)說
第三個(gè)返回false是因?yàn)橛昧薾ew關(guān)鍵字來開辟了新的空間描孟,i和j兩個(gè)對(duì)象分別指向堆區(qū)中的兩塊內(nèi)存空間。
我們可以跟蹤一下Integer的源碼砰左,看看到底怎么回事匿醒。在IDEA中,你只需要按住Ctrl然后點(diǎn)擊Integer缠导,就會(huì)自動(dòng)進(jìn)入jar包中對(duì)應(yīng)的類文件廉羔。
跟蹤到文件的700多行,你會(huì)看到這么一段僻造,感興趣可以仔細(xì)讀一下憋他,不用去讀也沒有關(guān)系,因?yàn)槟阒恍枰肋@是Java的一個(gè)緩存機(jī)制髓削。Integer類的內(nèi)部類緩存了-128到127的所有數(shù)字竹挡。(事實(shí)上,Integer類的緩存上限是可以通過修改系統(tǒng)來更改的立膛。了解就行了揪罕,不必去深究。)
為什么引入緩存機(jī)制
這回到了為什么引入基礎(chǔ)類型這個(gè)特例的問題上。我們看看Java語言規(guī)范是怎么規(guī)定的:
If the value p being boxed is an integer literal of type int between -128 and 127 inclusive (§3.10.1**), or the boolean literal true or false (§3.10.3**), or a character literal between '\u0000' and'\u007f' inclusive (§3.10.4**), then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.
Ideally, boxing a primitive value would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rule above is a pragmatic compromise, requiring that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, the rule disallows any assumptions about the identity of the boxed values on the programmer's part. This allows (but does not require) sharing of some or all of these references. Notice that integer literals of type long are allowed, but not required, to be shared.
This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.
事實(shí)上耸序,不光是Integer這么特別忍些,還包括boolean還有char類型。并且文章的最后提到了為了實(shí)現(xiàn)更少內(nèi)存的可能坎怪。
另一個(gè)特例:String
String是一個(gè)特殊的類罢坝,因?yàn)樗籪inal修飾符所修飾,是一個(gè)不可改變的類搅窿。當(dāng)然嘁酿,看過java源碼后你會(huì)發(fā)現(xiàn),基本類型的各個(gè)包裝類也被final所修飾男应。這里以String為例闹司。
我們來看這樣一個(gè)例子:
執(zhí)行第一句 : 常量區(qū)開辟空間存放“abc”,s1存放在棧中指向“abc”
執(zhí)行第二句沐飘,s2 也指向 “abc”游桩,
執(zhí)行第三句,因?yàn)椤癮bc”已經(jīng)存在耐朴,所以直接指向它借卧。
所以三個(gè)變量指向同一塊內(nèi)存地址,結(jié)果都為true筛峭。
當(dāng)s1內(nèi)容改變的時(shí)候铐刘。這個(gè)時(shí)候,常量區(qū)開辟新的空間存放“bcd”影晓,s1指向“bcd”镰吵,而s2和s3指向“abc”所以只有s2和s3相等。
這種情況下挂签,s1,s2,s3都是字符串常量疤祭,類似于基本數(shù)據(jù)類型。(如果執(zhí)行的是s1 = "abc",那么結(jié)果會(huì)都是true)
我們?cè)倏匆粋€(gè)例子:
執(zhí)行第一行代碼: 在堆里分配空間存放String對(duì)象饵婆,在常量區(qū)開辟空間存放常量“abc”画株,String對(duì)象指向常量,s1指向該對(duì)象啦辐。
執(zhí)行第二行代碼:s2指向上一步new出來的string對(duì)象谓传。
執(zhí)行第三行代碼: 在堆里分配新的空間存放String對(duì)象,新對(duì)象指向常量“abc”芹关,s3指向該對(duì)象续挟。
到這里,很明顯侥衬,s1和s2指向的是同一個(gè)對(duì)象接著就很詭異了诗祸,我們讓s1 依舊= “abc",但是結(jié)果s1和s2指向的地址不同了跑芳。
怎么回事呢?這就是String類的特殊之處了直颅,new出來的String不再是上面的字符串常量博个,而是字符串對(duì)象。
由于String類是不可改變的功偿,所以String對(duì)象也是不可改變的盆佣,我們每次給String賦值都相當(dāng)于執(zhí)行了一次new String(),然后讓變量指向這個(gè)新對(duì)象械荷,而不是在原來的對(duì)象上修改共耍。
當(dāng)然,java還提供了StringBuffer類吨瞎,這個(gè)是可以在原對(duì)象上做修改的痹兜。如果你需要修改原對(duì)象,那么請(qǐng)使用StringBuffer類颤诀。
引發(fā)的問題:值傳遞還是引用傳遞字旭?
java是值傳遞還是引用傳遞的呢?毫無疑問崖叫,java是值傳遞的谐算。那么什么又叫值傳遞和引用傳遞呢?
我們先來看一個(gè)例子:
這是一個(gè)很經(jīng)典的例子归露,我們希望調(diào)用了swap函數(shù)以后,a和b的值可以互換斤儿,但是事實(shí)上并沒有剧包。為什么會(huì)這樣呢阵子?
這就是因?yàn)閖ava是值傳遞的头镊。也就是說,我們?cè)谡{(diào)用一個(gè)需要傳遞參數(shù)的函數(shù)時(shí)表窘,傳遞給函數(shù)的參數(shù)并不是我們傳進(jìn)去的參數(shù)本身陕贮,而是它的副本堕油。說起來比較拗口,但是其實(shí)原理很簡(jiǎn)單肮之。我們可以這樣理解:
一個(gè)有形參的函數(shù)掉缺,當(dāng)別的函數(shù)調(diào)用它的時(shí)候,必須要傳遞數(shù)據(jù)戈擒。比如swap函數(shù)眶明,別的函數(shù)要調(diào)用swap就必須傳兩個(gè)整數(shù)過來。
這個(gè)時(shí)候筐高,有一個(gè)函數(shù)按耐不住寂寞搜囱,扔了兩個(gè)整數(shù)過來丑瞧,但是,swap函數(shù)有潔癖蜀肘,它不喜歡用別人的東西绊汹,于是它把傳過來的參數(shù)復(fù)制了一份,然后對(duì)復(fù)制的數(shù)據(jù)修修改改扮宠,而別人傳過來的參數(shù)動(dòng)根本沒動(dòng)西乖。
所以,當(dāng)swap函數(shù)執(zhí)行完畢之后涵卵,交換了的數(shù)據(jù)只是swap自己復(fù)制的那一份浴栽,而原來的數(shù)據(jù)沒變。
也可以理解為別的函數(shù)把數(shù)據(jù)傳遞給了swap函數(shù)的形參轿偎,最后改變的只是形參而實(shí)參沒變典鸡,所以不會(huì)起到任何效果。
我們?cè)賮砜匆粋€(gè)復(fù)雜一點(diǎn)的例子(Person類添加了get坏晦,set方法):
可以看到萝玷,我們把p1傳進(jìn)去,它并沒有被替換成新的對(duì)象昆婿。因?yàn)閏hange函數(shù)操作的不是p1這個(gè)引用本身球碉,而是這個(gè)引用的一個(gè)副本。
你依然可以理解為仓蛆,主函數(shù)將p1復(fù)制了一份然后變成了chagne函數(shù)的形參睁冬,最終指向新Person對(duì)象的是那個(gè)副本引用,而實(shí)參p1并沒有改變看疙。
再來看一個(gè)例子:
這次為什么就改變了呢豆拨?分析一下。
首先能庆,new了一個(gè)Person對(duì)象施禾,暫且叫他小明吧。然后p1指向小明搁胆。
小明10歲了弥搞,隨著時(shí)間的推移,小明的年齡要變了渠旁,調(diào)用了一下changgeAge方法攀例,把小明的引用傳了進(jìn)去。
傳遞的過程中顾腊,changgeAge也有潔癖肛度,于是復(fù)制了一份小明的引用,這個(gè)副本也指向小明投慈。
然后changgeAge通過自己的副本引用承耿,改變了小明的年齡冠骄。
由于是小明這個(gè)對(duì)象被改變了,所以所有小明的引用調(diào)用方法得到的年齡都會(huì)改變
所以就變了加袋。
最后簡(jiǎn)單的總結(jié)一下凛辣。
java的傳值過程,其實(shí)傳的是副本职烧,不管是變量還是引用扁誓。所以,不要期待把變量傳遞給一個(gè)函數(shù)來改變變量本身蚀之。
“+”是怎么連接字符串的蝗敢?
先拋個(gè)磚:對(duì)Java程序員來說,使用運(yùn)算符“+”來連接字符串是非常普遍的足删,當(dāng)“+”兩邊的操作數(shù)是String類型時(shí)(如果只有一個(gè)操作數(shù)是String類型寿谴,則系統(tǒng)也會(huì)將另外一個(gè)操作數(shù)轉(zhuǎn)換成String類型),就會(huì)執(zhí)行字符串連接的運(yùn)算失受。但是讶泰,運(yùn)算符“+”是怎樣連接String對(duì)象的呢?編譯器又是如何實(shí)現(xiàn)的呢?
之后我再來補(bǔ)這個(gè)內(nèi)容拂到,先發(fā)表啦痪署。
浮點(diǎn)類型
浮點(diǎn)類型用于表示有小數(shù)部分的數(shù)值。在Java中有兩種浮點(diǎn)類型兄旬,一個(gè)是4字節(jié)的float狼犯,一個(gè)是8字節(jié)的double。我們平時(shí)用來編寫程序用來表示增長率领铐、物品重量等方面也非常有用悯森。不過,在使用浮點(diǎn)類型時(shí)罐孝,也需要留意一些問題。
浮點(diǎn)類型只是近似的存儲(chǔ)
請(qǐng)問一個(gè)問題:0.1+0.2等于多少肥缔?請(qǐng)不要慌著報(bào)答案莲兢,我沒有開玩笑的意思,看一下Java給出的答案你就知道了:
結(jié)果似乎有些令人驚訝续膳,這么簡(jiǎn)單的算術(shù)竟然也會(huì)算錯(cuò)改艇。
其實(shí),這并不是計(jì)算錯(cuò)誤坟岔,這只是浮點(diǎn)數(shù)類型存儲(chǔ)的問題谒兄。計(jì)算機(jī)使用二進(jìn)制來存儲(chǔ)數(shù)據(jù),而二進(jìn)制無法準(zhǔn)確的表示分?jǐn)?shù) 1/10 社付,就像使用十進(jìn)制時(shí)承疲,無法準(zhǔn)確地表示 1/3 一樣邻耕。
數(shù)量級(jí)差很大的浮點(diǎn)運(yùn)算
當(dāng)浮點(diǎn)數(shù)值的數(shù)量級(jí)相差很大的時(shí)候,運(yùn)算又會(huì)有什么問題呢燕鸽?
又發(fā)生了預(yù)期外的結(jié)果兄世。從輸出結(jié)果來看,f3竟然和f4是相等的啊研,也就是意味著對(duì)f3+1并沒有改變f3的值御滩。
這同樣是因?yàn)楦↑c(diǎn)數(shù)的存儲(chǔ)造成的,二進(jìn)制所能表示的兩個(gè)相鄰的浮點(diǎn)值之間存在一定的空隙党远。浮點(diǎn)值越大削解,這個(gè)間隙也會(huì)越大。當(dāng)浮點(diǎn)值大道一定程度的時(shí)沟娱,如果對(duì)浮點(diǎn)值的改變很蟹胀浴(例如上面的30000000+1),就不足以使浮點(diǎn)值發(fā)生改變花沉。就好比蒸發(fā)掉大海中的一滴水柳爽,大海還是大海,幾乎不存在變化碱屁。
如果想要準(zhǔn)確的存儲(chǔ)磷脯,就去使用BigDecimal吧,有必要了解的可以去自行百度娩脾,這里就不做過多介紹了赵誓,已經(jīng)是Java封裝好的類庫了
拋出一個(gè)有趣的問題
我們知道,在Java中柿赊,long類型占用了8個(gè)字節(jié)俩功,float類型占用了4個(gè)字節(jié)。
照理來說碰声,long類型的容量應(yīng)該比float大許多诡蜓,然而事實(shí)正好相反,float反而擁有比8字節(jié)long類型更大的取值范圍胰挑。這同樣是因?yàn)楦↑c(diǎn)數(shù)的存儲(chǔ)格式造成的蔓罚。有興趣的可以去自行百度了解。
參考資料:
http://www.reibang.com/p/39753aad9a38 瞻颂,原文作者:CleverFan
《Java深入解析》——梁勇著
《Effective Java》——第二版
《Java核心技術(shù) 卷I》——第九版
《Java編程思想》——第四版
歡迎轉(zhuǎn)載豺谈,轉(zhuǎn)載請(qǐng)注明出處!
簡(jiǎn)書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號(hào):wmyskxz
分享自己的學(xué)習(xí) & 學(xué)習(xí)資料 & 生活
想要交流的朋友也可以加qq群:3382693