Java基礎(chǔ)教程之對(duì)象引用
我們之前一直在使用“對(duì)象”這個(gè)概念,但沒(méi)有探討對(duì)象在內(nèi)存中的具體存儲(chǔ)方式。這方面的討論將引出“對(duì)象引用”(object reference)這一重要概念。
對(duì)象引用
我們沿用之前定義的Human類(lèi),并有一個(gè)Test類(lèi):
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human(160);
}
}
class Human
{
/**
* constructor
*/
public Human(int h)
{
this.height = h;
}
/**
* accessor
*/
public int getHeight()
{
return this.height;
}
/**
* mutator
*/
public void growHeight(int h)
{
this.height = this.height + h;
}
private int height;
}
外部可以調(diào)用類(lèi)來(lái)創(chuàng)建對(duì)象,比如上面在Test類(lèi)中:
Human aPerson = new Human(160);
創(chuàng)建了一個(gè)Human類(lèi)的對(duì)象aPerson。
上面是一個(gè)非常簡(jiǎn)單的表述,但我們有許多細(xì)節(jié)需要深入:
1.首先看等號(hào)的右側(cè)。new是在內(nèi)存中為對(duì)象開(kāi)辟空間。具體來(lái)說(shuō),new是在內(nèi)存的堆(heap)上為對(duì)象開(kāi)辟空間。這一空間中,保存有對(duì)象的數(shù)據(jù)和方法。
2.再看等號(hào)的左側(cè)。aPerson指代一個(gè)Human對(duì)象,被稱(chēng)為對(duì)象引用(reference)。實(shí)際上,aPerson并不是對(duì)象本身,而是類(lèi)似于一個(gè)指向?qū)ο蟮闹羔?。aPerson存在于內(nèi)存的棧(stack)中。
3.當(dāng)我們用等號(hào)賦值時(shí),是將右側(cè)new在堆中創(chuàng)建對(duì)象的地址賦予給對(duì)象引用。
這里的內(nèi)存,指的是JVM (Java Virtual Machine)虛擬出來(lái)的Java進(jìn)程內(nèi)存空間。內(nèi)存的堆和棧概念可參考Linux從程序到進(jìn)程。
棧的讀取速度比堆快,但棧上存儲(chǔ)的數(shù)據(jù)受到有效范圍的限制。在C語(yǔ)言中,當(dāng)一次函數(shù)調(diào)用結(jié)束時(shí),相應(yīng)的棧幀(stack frame)要?jiǎng)h除,棧幀上存儲(chǔ)的參量和自動(dòng)變量就消失了。Java的棧也受到同樣的限制,當(dāng)一次方法調(diào)用結(jié)束,該方法存儲(chǔ)在棧上的數(shù)據(jù)將清空。在 Java中,所有的(普通)對(duì)象都儲(chǔ)存在堆上。因此,new關(guān)鍵字的完整含義是,在堆上創(chuàng)建對(duì)象。
基本類(lèi)型(primitive type)的對(duì)象,比如int, double,保存在棧上。當(dāng)我們聲明基本類(lèi)型時(shí),不需要new。一旦聲明,Java將在棧上直接存儲(chǔ)基本類(lèi)型的數(shù)據(jù)。所以,基本類(lèi)型的變量名表示的是數(shù)據(jù)本身,不是引用。
引用和對(duì)象的關(guān)系就像風(fēng)箏和人。我們看天空時(shí)(程序里寫(xiě)的),看到的是風(fēng)箏(引用),但風(fēng)箏下面對(duì)應(yīng)的,是人(對(duì)象):
引用和對(duì)象分離;引用指向?qū)ο?/p>
盡管引用和對(duì)象是分離的,但我們所有通往對(duì)象的訪(fǎng)問(wèn)必須經(jīng)過(guò)引用這個(gè)“大門(mén)”,比如以 引用.方法() 的方式訪(fǎng)問(wèn)對(duì)象的方法。在Java中,我們不能跳過(guò)引用去直接接觸對(duì)象。再比如,對(duì)象a的數(shù)據(jù)成員如果是一個(gè)普通對(duì)象b,a的數(shù)據(jù)成員保存的是指向?qū)ο骲的引用 (如果是基本類(lèi)型變量,那么a的數(shù)據(jù)成員保存的是基本類(lèi)型變量本身了)。
在Java中,引用起到了指針的作用,但我們不能直接修改指針的值,比如像C語(yǔ)言那樣將指針值加1。我們只能通過(guò)引用執(zhí)行對(duì)對(duì)象的操作。這樣的設(shè)計(jì)避免了許多指針可能引起的錯(cuò)誤。
引用的賦值
當(dāng)我們將一個(gè)引用賦值給另一個(gè)引用時(shí),我們實(shí)際上復(fù)制的是對(duì)象的地址。兩個(gè)引用將指向同一對(duì)象。比如 dummyPerson=aPerson;,將導(dǎo)致:
一個(gè)對(duì)象可以有多個(gè)引用 (一個(gè)人可以放多個(gè)風(fēng)箏)。當(dāng)程序通過(guò)某個(gè)引用修改對(duì)象時(shí),通過(guò)其他引用也可以看到該修改。我們可以用以下Test類(lèi)來(lái)測(cè)試實(shí)際效果:
public class Test
{
public static void main(String[] args)
{
Human aPerson = new Human(160);
Human dummyPerson = aPerson;
System.out.println(dummyPerson.getHeight());
aPerson.growHeight(20);
System.out.println(dummyPerson.getHeight());
}
}
我們對(duì)aPerson的修改將影響到dummyPerson。這兩個(gè)引用實(shí)際上指向同一對(duì)象。
所以,將一個(gè)引用賦值給另一個(gè)引用,并不能復(fù)制對(duì)象本身。我們必須尋求其他的機(jī)制來(lái)復(fù)制對(duì)象。
垃圾回收
隨著方法調(diào)用的結(jié)束,引用和基本類(lèi)型變量會(huì)被清空。由于對(duì)象存活于堆,所以對(duì)象所占據(jù)的內(nèi)存不會(huì)隨著方法調(diào)用的結(jié)束而清空。進(jìn)程空間可能很快被不斷創(chuàng)建的對(duì)象占滿(mǎn)。Java內(nèi)建有垃圾回收(garbage collection)機(jī)制,用于清空不再使用的對(duì)象,以回收內(nèi)存空間。
垃圾回收的基本原則是,當(dāng)存在引用指向某個(gè)對(duì)象時(shí),那么該對(duì)象不會(huì)被回收; 當(dāng)沒(méi)有任何引用指向某個(gè)對(duì)象時(shí),該對(duì)象被清空。它所占據(jù)的空間被回收。
上圖假設(shè)了某個(gè)時(shí)刻JVM中的內(nèi)存狀態(tài)。Human Object有三個(gè)引用: 來(lái)自棧的aPerson和dummyPerson,以及另一個(gè)對(duì)象的數(shù)據(jù)成員president。而Club Object沒(méi)有引用。如果這個(gè)時(shí)候垃圾回收啟動(dòng),那么Club Object將被清空,而Human Object來(lái)自Club Object的引用(president)也隨之被刪除。
垃圾回收是Java中重要的機(jī)制,它直接影響了Java的運(yùn)行效率。我將在以后深入其細(xì)節(jié)。
參數(shù)傳遞
當(dāng)我們分離了引用和對(duì)象的概念后,Java方法的參數(shù)傳遞機(jī)制實(shí)際上非常清晰: Java的參數(shù)傳遞為值傳遞。也就是說(shuō),當(dāng)我們傳遞一個(gè)參數(shù)時(shí),方法將獲得該參數(shù)的一個(gè)拷貝。
實(shí)際上,我們傳遞的參數(shù),一個(gè)是基本類(lèi)型的變量,另一個(gè)為對(duì)象的引用。
基本類(lèi)型變量的值傳遞,意味著變量本身被復(fù)制,并傳遞給Java方法。Java方法對(duì)變量的修改不會(huì)影響到原變量。
引用的值傳遞,意味著對(duì)象的地址被復(fù)制,并傳遞給Java方法。Java方法根據(jù)該引用的訪(fǎng)問(wèn)將會(huì)影響對(duì)象。
在這里有另一個(gè)值得一提的情況: 我們?cè)诜椒▋?nèi)部使用new創(chuàng)建對(duì)象,并將該對(duì)象的引用返回。如果該返回被一個(gè)引用接收,由于對(duì)象的引用不為0,對(duì)象依然存在,不會(huì)被垃圾回收。
總結(jié)
new
引用,對(duì)象
被垃圾回收的條件
參數(shù): 值傳遞
- Java返回可變引用對(duì)象問(wèn)題整理
- Java對(duì)象的四種引用方式實(shí)例分析
- 面試官:詳細(xì)談?wù)凧ava對(duì)象的4種引用方式
- 詳解Java對(duì)象的強(qiáng)、軟、弱和虛引用+ReferenceQueue
- Java中的對(duì)象和引用詳解
- Java多態(tài)和實(shí)現(xiàn)接口的類(lèi)的對(duì)象賦值給接口引用的方法(推薦)
- JAVA中的引用與對(duì)象詳解
- 對(duì)Java的面對(duì)對(duì)象編程中對(duì)象和引用以及內(nèi)部類(lèi)的理解
- 解析Java的JNI編程中的對(duì)象引用與內(nèi)存泄漏問(wèn)題
- Java中的對(duì)象和對(duì)象引用實(shí)例淺析
- 淺析Java 對(duì)象引用和對(duì)象本身
相關(guān)文章
Java實(shí)現(xiàn)簡(jiǎn)易俄羅斯方塊
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)易俄羅斯方塊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06談?wù)凷pring Boot 數(shù)據(jù)源加載及其多數(shù)據(jù)源簡(jiǎn)單實(shí)現(xiàn)(小結(jié))
這篇文章主要介紹了談?wù)凷pring Boot 數(shù)據(jù)源加載及其多數(shù)據(jù)源簡(jiǎn)單實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04IDEA Java win10環(huán)境配置的圖文教程
這篇文章主要介紹了IDEA Java win10環(huán)境配置,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07ArrayList在for循環(huán)中使用remove方法移除元素方法介紹
這篇文章主要介紹了ArrayList在for循環(huán)中使用remove方法移除元素的內(nèi)容,介紹了具體代碼實(shí)現(xiàn),需要的朋友可以參考下。2017-09-09JAVA 線(xiàn)程通信相關(guān)知識(shí)匯總
這篇文章主要介紹了JAVA 線(xiàn)程通信相關(guān)知識(shí),文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06java根據(jù)模板導(dǎo)出PDF的詳細(xì)實(shí)現(xiàn)過(guò)程
前段時(shí)間因?yàn)橄嚓P(guān)業(yè)務(wù)需求需要后臺(tái)生成pdf文件,所以下面這篇文章主要給大家介紹了關(guān)于java根據(jù)模板導(dǎo)出PDF的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02java實(shí)現(xiàn)基于Tcp的socket聊天程序
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)基于Tcp的socket聊天程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07springboot 整合fluent mybatis的過(guò)程,看這篇夠了
這篇文章主要介紹了springboot 整合fluent mybatis的過(guò)程,配置數(shù)據(jù)庫(kù)連接創(chuàng)建數(shù)據(jù)庫(kù)的詳細(xì)代碼,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08