万物皆对象java_又一次认识java(一) ---- 万物皆对象
假設(shè)你現(xiàn)實(shí)中沒有對(duì)象。至少你在java世界里會(huì)有茫茫多的對(duì)象,聽起來是不是非常激動(dòng)呢?
對(duì)象,引用,類與現(xiàn)實(shí)世界
現(xiàn)實(shí)世界里有許很多多的生物,非生物,跑的跳的飛的,過去的如今的未來的,令人眼花繚亂。我們編程的目的,就是解決現(xiàn)實(shí)生活中的問題。所以不可避免的我們要和現(xiàn)實(shí)世界中各種奇怪的東西打交道。
在現(xiàn)實(shí)世界里。你新認(rèn)識(shí)了一個(gè)朋友,你知道他長(zhǎng)什么樣,知道了他的名字年齡。地址。
知道他喜歡干什么有什么特長(zhǎng)。你想用java語言描寫敘述一下這個(gè)人。你應(yīng)該怎么做呢?
這個(gè)時(shí)候。就有了類的概念。
每個(gè)類相應(yīng)現(xiàn)實(shí)世界中的某一事物。比方現(xiàn)實(shí)世界中有人。
那么我們就創(chuàng)建一個(gè)關(guān)于“人”的類。
每個(gè)人都有名字。都有地址等等個(gè)人信息。
那么我們就在“人”的類里面增加這些屬性。
每個(gè)人都會(huì)吃,會(huì)走路,那么我們就在“人”的類里面增加吃和走的方法。
當(dāng)這個(gè)世界又迎來了一個(gè)新生命,我們就能夠“new”一個(gè)“人”,“new”出來的就叫”對(duì)象“。
每個(gè)人一出生,父母就會(huì)給他取個(gè)名字。
在程序里,我們須要用一種方式來操作這個(gè)“對(duì)象”,于是。就出現(xiàn)了引用。我們通過引用來操作對(duì)象。設(shè)置對(duì)象的屬性。操作對(duì)象的方法。
這就是最主要的面向?qū)ο蟆?/p>
【 現(xiàn)實(shí)世界的事物】 —抽象—> 【類 】—new—>【對(duì)象 】
從創(chuàng)建一個(gè)對(duì)象開始
創(chuàng)建對(duì)象的前提是先得有一個(gè)類。
我們先自己創(chuàng)建一個(gè)person類。
//Person類
public class Person {
private String name;
private int age;
public void eat(){
System.out.println("i am eating");
}
}
創(chuàng)建一個(gè)person對(duì)象。
Person p = new Person();
怎么理解這句簡(jiǎn)單的代碼呢?
new Person :一個(gè)Person類型的對(duì)象
() : 這個(gè)括號(hào)相當(dāng)于調(diào)用了person的無參構(gòu)造方法
p : Person對(duì)象的引用
有的人會(huì)覺得p就是new出來的Person對(duì)象。
這是錯(cuò)誤的理解,p僅僅是一個(gè)Person對(duì)象的引用而已。那么問題來了,什么是引用?什么又是對(duì)象呢?這個(gè)要從內(nèi)存說起。
創(chuàng)建對(duì)象的過程
java大體上會(huì)把內(nèi)存分為四塊區(qū)域:堆,棧。靜態(tài)區(qū)。常量區(qū)。
堆 : 位于RAM中,用于存放全部的java對(duì)象。
棧 : 位于RAM中,引用就存在于棧中。
靜態(tài)區(qū) : 位于RAM中。被static修飾符修飾的變量會(huì)被放在這里
常量區(qū) :位于ROM中, 非常明顯。放常量的。
事實(shí)上,我們不須要關(guān)心java的對(duì)象,變量究竟存在了哪里。由于jvm會(huì)幫我們處理好這些。
可是理解了這些。有助于提高我們的水平。
當(dāng)運(yùn)行這句代碼的時(shí)候。
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è)新的引用,保存在棧中,引用的地址也指向Person的地址。
這個(gè)時(shí)候。你通過p2來改變Person對(duì)象的狀態(tài),也會(huì)改變p的結(jié)果。由于它們指向同一個(gè)對(duì)象。(String除外。之后會(huì)專門講String)
此時(shí)。內(nèi)存中是這樣的:
用一種非常通俗的方式來解說一下引用和對(duì)象。
大家都應(yīng)該用過windows吧。win有一個(gè)奇妙的東西叫做快捷方式。
我們桌面的圖標(biāo)大部分都是快捷方式。它并非我們安裝在電腦上的應(yīng)用的可運(yùn)行文件(不是.exe文件),那么為什么點(diǎn)擊它能夠打開應(yīng)用程序呢?這個(gè)我不用講了把。
我們的對(duì)象和引用就和快捷方式和它連接的文件一樣。
我們不直接對(duì)文件進(jìn)行操作,而是通過快捷方式來進(jìn)行操作。
快捷方式不能獨(dú)立存在,同樣,引用也不能獨(dú)立存在(你能夠僅僅創(chuàng)建一個(gè)引用。可是當(dāng)你要使用它的時(shí)候必須得給它賦值。否則它將毫無用處)。
一個(gè)文件能夠有多個(gè)快捷方式。同樣一個(gè)對(duì)象也能夠有多個(gè)引用。而一個(gè)引用僅僅能同一時(shí)候相應(yīng)一個(gè)對(duì)象。
在java里,“=”不能被看成是一個(gè)賦值語句。它不是在把一個(gè)對(duì)象賦給另外一個(gè)對(duì)象,它的運(yùn)行過程實(shí)質(zhì)上是將右邊對(duì)象的地址傳給了左邊的引用,使得左邊的引用指向了右邊的對(duì)象。java表面上看起來沒有指針。但它的引用事實(shí)上質(zhì)就是一個(gè)指針。在java里,“=”語句不應(yīng)該被翻譯成賦值語句,由于它所運(yùn)行的確實(shí)不是一個(gè)簡(jiǎn)單的賦值過程。而是一個(gè)傳地址的過程,被譯成賦值語句會(huì)造成非常多誤解,譯得不準(zhǔn)確。
特例:基本數(shù)據(jù)類型
為什么會(huì)有特例呢?由于用new操作符創(chuàng)建的對(duì)象會(huì)存在堆里,二在堆里開辟空間等行為效率較操作棧要低。而我們平時(shí)寫代碼的時(shí)候會(huì)經(jīng)常創(chuàng)建一些“小變量”。比方int i = 1;假設(shè)每次都用Interger來new一個(gè),效率不是非常高而且浪費(fèi)內(nèi)存。
所以針對(duì)這些情況。java提供了“基本數(shù)據(jù)類型”,基本數(shù)據(jù)類型一共同擁有八種,每個(gè)基本數(shù)據(jù)類型存放在棧中。而他們的值存放在常量區(qū)中。
舉個(gè)樣例:
int i = 2;
int j = 2;
我們須要知道的是,在常量區(qū)中,同樣的常量?jī)H僅會(huì)存在一個(gè)。當(dāng)運(yùn)行第一句代碼時(shí)。先查找常量區(qū)中有沒有2,沒有。則開辟一個(gè)空間存放2。然后在棧中存入一個(gè)變量i,讓i指向2;
運(yùn)行第二句的時(shí)候,查找發(fā)現(xiàn)2已經(jīng)存在了,所以就不開辟新空間了。直接在棧中保存一個(gè)新變量j。讓j指向2。
當(dāng)然,java堆每個(gè)基本數(shù)據(jù)類型都提供了相應(yīng)的包裝類。
我們依然能夠用new操作符來創(chuàng)建我們想要的變量。
Integer i = new Integer(1);
Integer j = new Integer(1);
可是,用new操作符創(chuàng)建的對(duì)象是不同的,也就是說,此時(shí),i和j指向不同的內(nèi)存地址。由于每次調(diào)用new操作符。都會(huì)在堆開辟新的空間。
當(dāng)然,說到基本數(shù)據(jù)類型,不得不提一下java的經(jīng)典設(shè)計(jì)。
先看一段代碼:
為什么一個(gè)是true一個(gè)是false呢?
我就不講了,應(yīng)該都知道吧。我就貼一個(gè)Integer的源代碼(jdk1.8)吧。
Integer 類的內(nèi)部定義了一個(gè)內(nèi)部類,緩存了從-128到127的全部數(shù)字,所以,你懂得。
又一個(gè)特例 :String
String是一個(gè)特殊的類,由于它被final修飾符所修飾。是一個(gè)不可改變的類。當(dāng)然。看過java源代碼后你會(huì)發(fā)現(xiàn),基本類型的各個(gè)包裝類也被final所修飾。這里以String為例。
我們來看這樣一個(gè)樣例
運(yùn)行第一句 : 常量區(qū)開辟空間存放“abc”,s1存放在棧中指向“abc”
運(yùn)行第二句,s2 也指向 “abc”。
運(yùn)行第三句,由于“abc”已經(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ù)類型。(假設(shè)運(yùn)行的是s1 = “abc”,那么結(jié)果會(huì)都是true)
我們?cè)倏匆粋€(gè)樣例:
運(yùn)行第一行代碼: 在堆里分配空間存放String對(duì)象,在常量區(qū)開辟空間存放常量“abc”,String對(duì)象指向常量。s1指向該對(duì)象。
運(yùn)行第二行代碼:s2指向上一步new出來的string對(duì)象。
運(yùn)行第三行代碼: 在堆里分配新的空間存放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)于運(yùn)行了一次new String(),然后讓變量指向這個(gè)新對(duì)象,而不是在原來的對(duì)象上改動(dòng)。
當(dāng)然,java還提供了StringBuffer類,這個(gè)是能夠在原對(duì)象上做改動(dòng)的。假設(shè)你須要改動(dòng)原對(duì)象。那么請(qǐng)使用StringBuffer類。
值傳遞和引用傳遞的戰(zhàn)爭(zhēng)
java是值傳遞還是引用傳遞的呢?毫無疑問,java是值傳遞的。那么什么又叫值傳遞和引用傳遞呢?
我們先來看一個(gè)樣例:
這是一個(gè)非常經(jīng)典的樣例,我們希望調(diào)用了swap函數(shù)以后,a和b的值能夠互換。可是事實(shí)上并沒有。為什么會(huì)這樣呢?
這就是由于java是值傳遞的。也就是說,我們?cè)谡{(diào)用一個(gè)須要傳遞參數(shù)的函數(shù)時(shí),傳遞給函數(shù)的參數(shù)并非我們傳進(jìn)去的參數(shù)本身。而是它的副本。說起來比較拗口,可是事實(shí)上原理非常easy。我們能夠這樣理解:
一個(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ù)修改動(dòng)改。而別人傳過來的參數(shù)動(dòng)根本沒動(dòng)。
所以,當(dāng)swap函數(shù)運(yùn)行完成之后,交換了的數(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ì)象。由于change函數(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ù)來改變變量本身。
對(duì)象的強(qiáng)引用,軟引用,弱引用和虛引用
Java中是JVM負(fù)責(zé)內(nèi)存的分配和回收,這樣盡管使用方便,程序不用再像使用c那樣擔(dān)心內(nèi)存,但同一時(shí)候也是它的缺點(diǎn)(不夠靈活)。
為了解決內(nèi)存操作不靈活這個(gè)問題,能夠採(cǎi)用軟引用等方法。
先介紹一下這四種引用:
強(qiáng)引用
曾經(jīng)我們使用的大部分引用實(shí)際上都是強(qiáng)引用。這是使用最普遍的引用。
假設(shè)一個(gè)對(duì)象具有強(qiáng)引用,那就相似于不可缺少的生活用品。垃圾回收器絕不會(huì)回收它。當(dāng)內(nèi)存空 間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤。使程序異常終止,也不會(huì)靠任意回收具有強(qiáng)引用的對(duì)象來解決內(nèi)存不足問題。
軟引用(SoftReference)
假設(shè)一個(gè)對(duì)象僅僅具有軟引用。那就相似于可有可物的生活用品。假設(shè)內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它,假設(shè)內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。僅僅要垃圾回收器沒有回收它。該對(duì)象就能夠被程序使用。
軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的快速緩存。
軟引用能夠和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用。假設(shè)軟引用所引用的對(duì)象被垃圾回收,JAVA虛擬機(jī)就會(huì)把這個(gè)軟引用增加到與之關(guān)聯(lián)的引用隊(duì)列中。
弱引用(WeakReference)
假設(shè)一個(gè)對(duì)象僅僅具有弱引用,那就相似于可有可物的生活用品。
弱引用與軟引用的差別在于:僅僅具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它 所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了僅僅具有弱引用的對(duì)象,無論當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。只是。由于垃圾回收器是一個(gè)優(yōu)先級(jí)非常低的線程, 因此不一定會(huì)非常快發(fā)現(xiàn)那些僅僅具有弱引用的對(duì)象。
弱引用能夠和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用。假設(shè)弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用增加到與之關(guān)聯(lián)的引用隊(duì)列中。
虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設(shè)。與其它幾種引用都不同。虛引用并不會(huì)決定對(duì)象的生命周期。假設(shè)一個(gè)對(duì)象僅持有虛引用,那么它就和沒有不論什么引用一樣。在不論什么時(shí)候都可能被垃圾回收。
虛引用主要用來跟蹤對(duì)象被垃圾回收的活動(dòng)。
虛引用與軟引用和弱引用的一個(gè)差別在于:虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用。
當(dāng)垃 圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),假設(shè)發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用增加到與之關(guān)聯(lián)的引用隊(duì)列中。
程序能夠通過推斷引用隊(duì)列中是 否已經(jīng)增加了虛引用,來了解被引用的對(duì)象是否將要被垃圾回收。程序假設(shè)發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被增加到引用隊(duì)列,那么就能夠在所引用的對(duì)象的內(nèi)存被回收之前採(cǎi)取必要的行動(dòng)。
在實(shí)際開發(fā)中。弱引用和虛引用不經(jīng)常使用,用得比較多的是軟引用,由于它能夠加速jvm的回收。
軟引用的使用方式:
關(guān)于軟引用,我之后會(huì)單獨(dú)寫一篇文章,所以這里先一筆帶過。
對(duì)象的復(fù)制
java除了用new來創(chuàng)建對(duì)象,還能夠通過clone來復(fù)制對(duì)象。
那么這兩種方式有什么同樣和不同呢?
new
new操作符的本意是分配內(nèi)存。
程序運(yùn)行到new操作符時(shí),首先去看new操作符后面的類型,由于知道了類型。才干知道要分配多大的內(nèi)存空間。
分配完內(nèi)存之后,再調(diào)用構(gòu)造函數(shù),填充對(duì)象的各個(gè)域,這一步叫做對(duì)象的初始化,構(gòu)造方法返回后,一個(gè)對(duì)象創(chuàng)建完成,能夠把他的引用(地址)公布到外部。在外部就能夠使用這個(gè)引用操縱這個(gè)對(duì)象。
clone
clone在第一步是和new相似的, 都是分配內(nèi)存。調(diào)用clone方法時(shí),分配的內(nèi)存和源對(duì)象(即調(diào)用clone方法的對(duì)象)同樣。然后再使用原對(duì)象中相應(yīng)的各個(gè)域。填充新對(duì)象的域, 填充完成之后。clone方法返回,一個(gè)新的同樣的對(duì)象被創(chuàng)建,同樣能夠把這個(gè)新對(duì)象的引用公布到外部。
怎樣利用clone的方式來得到一個(gè)對(duì)象呢?
看代碼:
對(duì)Person類做了一些改動(dòng)
看實(shí)現(xiàn)代碼:
這樣就得到了一個(gè)和原來一樣的新對(duì)象。
深復(fù)制和淺復(fù)制
可是,細(xì)心而且善于思考的人可能一經(jīng)發(fā)現(xiàn)了一個(gè)問題。
age是一個(gè)基本數(shù)據(jù)類型,支架clone沒什么問題。可是name可是一個(gè)String類型的啊。我們clone后的對(duì)象里的name和原來對(duì)象的name是不是指向同一個(gè)字符串常量呢?
做個(gè)試驗(yàn):
果然,是同一個(gè)對(duì)象。假設(shè)你不能理解。那么看這個(gè)圖。
事實(shí)上假設(shè)僅僅是String還好,由于String的不可變性,當(dāng)你隨便改動(dòng)一個(gè)值的時(shí)候,他們就會(huì)指向不同的地址了,可是除了String,其它都是可變的。這就危急了。
上面的這樣的情況。就是淺克隆。
這樣的方式在你的屬性列表中有其它對(duì)象的引用的時(shí)候事實(shí)上是非常危急的。
所以,我們須要深克隆。也就是說我們須要將這個(gè)對(duì)象里的對(duì)象也clone一份。怎么做呢?
在內(nèi)存中通過字節(jié)流的拷貝是比較easy實(shí)現(xiàn)的。把母對(duì)象寫入到一個(gè)字節(jié)流中,再?gòu)淖止?jié)流中將其讀出來,這樣就能夠創(chuàng)建一個(gè)新的對(duì)象了。而且該新對(duì)象與母對(duì)象之間并不存在引用共享的問題,真正實(shí)現(xiàn)對(duì)象的深拷貝。
//使用該工具類的對(duì)象必須要實(shí)現(xiàn) Serializable 接口,否則是沒有辦法實(shí)現(xiàn)克隆的。
public class CloneUtils {
public static T clone(T obj){
T cloneObj = null;
try {
//寫入字節(jié)流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配內(nèi)存,寫入原始對(duì)象,生成新對(duì)象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新對(duì)象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
使用該工具類的對(duì)象僅僅要實(shí)現(xiàn) Serializable 接口就可實(shí)現(xiàn)對(duì)象的克隆,無須繼承 Cloneable 接口實(shí)現(xiàn) clone() 方法。
測(cè)試一下:
非常完美
這個(gè)時(shí)候,Person類實(shí)現(xiàn)了Serializable接口
是否使用復(fù)制,深復(fù)制還是淺復(fù)制看情況來使用。
關(guān)于序列化與反序列化以后會(huì)講。
這篇文章到這里就臨時(shí)告一段落了,興許有補(bǔ)充的話我會(huì)繼續(xù)補(bǔ)充,有錯(cuò)誤的話,我也會(huì)及時(shí)改正。歡迎大家提出問題。
總結(jié)
以上是生活随笔為你收集整理的万物皆对象java_又一次认识java(一) ---- 万物皆对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中信京东小白卡额度一般是多少
- 下一篇: java 中while编译之后_从APK