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