Java中對象和引用的理解


偶然想起Java中對象和引用的基本概念,為了加深下對此的理解和認識,特地整理一下相關的知識點,通過具體實例從兩者的概念和區別兩方面去更形象的認識理解,再去記憶。

 

一、對象和引用的概念: 
在Java中萬物皆對象,比如我們定義一個簡單的動物類:

class Animal {
    String count;
    String weight;
    ....
}

 

有了這個Animal類之后,我們可以來創建一個Animal對象:

Animal an = new Animal();

 

我們把編寫這個語句的動作就稱作創建一個對象,細化這個動作為: 
1. 右面的”new Animal”,是以Animal類為模板的,在堆空間里創建一個Animal對象; 
2. 末尾的”( )”代表着:在對象創建之后,立即調用Animal類的構造函數,對新生成的對象進行初始化。(如果沒構造函數,Java會有一個默認的構造函數的); 
3. 左面的”Animal an” 創建了一個Animal類引用變量。即以后可以用來指向Animal對象的對象引用; 
4. “=” 操作符使對象引用指向剛才創建的那個Animal對象。 
拆分開也就是:等同於

Animal an;
an = new Animal();

 

 

有兩個實體:一個是對象引用變量;一個是對象本身。 
在java中,都是通過引用來操縱對象的,這也是兩者的區別。

二、對象和引用的區別: 
1、關聯性: 
1). 當對象的引用變量指向對象時,兩者就互相聯系起來,改變引用的屬性,就會改變對象的屬性; 
2). 如果同一個對象被多個引用變量引用的話,則這些引用變量將共同影響這個對象本身; 
3). 在java中,都是通過引用來操縱對象的。

2、差異性: 
1). 一個對象可以被不同的引用變量來操縱,同時一個引用變量也可以指向不同的對象,但是同一時刻下只能指向一個對象。 
2). 從存儲空間上來看,對象和引用也是相互獨立的,對象一般存儲在堆中,而引用存儲在堆棧中(存儲速度而更快)。

對於引用變量的深層含義,未必在初學的時候就能深刻理解, 
所以理解好下面這兩句話的真正含義非常重要

Case cc=new Case();

Case cc;
cc=new Case();

 

1.先搞清楚什么是堆,什么是棧。 
Java開辟了兩類存儲區域,對比二者的特點

存儲區域 存儲內容 優點 缺點 回收
基本類型的變量和對象的引用變量 存取速度比堆要快,僅次於寄存器,棧數據可以共享 存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量 當超過變量的作用域后,Java會自動釋放掉該變量,內存空間可以立即被另作他用
由new等指令創建的對象和數組 可以動態地分配內存大小,生存期也不必事先告訴編譯器 由於要在運行時動態分配內存,存取速度較慢 由Java虛擬機的自動垃圾回收器來回收不再使用的數據

堆棧的存儲特點決定了其中存儲的數據類型。

注意,棧內存儲的除了基本類型的變量(String, int 這種類型的變量)還會存儲對象的引用變量。java中,引用變量實際上是一個指針,它指向的是堆內存中對象實例。

引用變量就相當於是為數組或對象起的一個名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。

2.給引用變量賦值

回過頭再來看代碼

實際上里面分解成了四個步驟。

Case cc; '''在棧內存里面開辟了空間給引用變量cc,這時cc=null'''
cc=new Case();
''' 1. new Case()在堆內存里面開辟了空間給Case類的對象,這個對象沒有名字 2. Case()隨即調用了Case類的構造函數 3. 把對象的地址在堆內存的地址給引用變量cc '''

 

 

這樣我們就明確了:

  • Java中,這里的“=”並不是賦值的意思,而是把對象的地址傳遞給變量;
  • 對象創建出來,其實連名字都沒有,因此必須通過引用變量來對其進行操作。

為了形象地說明對象、引用及它們之間的關系,可以做一個或許不很妥當的比喻。對象好比是一只很大的氣球,大到我們抓不住它。引用變量是一根繩, 可以用來系汽球

緊接着就會問,引用變量是怎么傳遞的呢? 
這就涉及到Java唯一的參數傳遞方式——按值傳遞

看下面一段代碼:

public class ObjectRef {

    '''基本類型的參數傳遞'''
    public static void testBasicType(int m) {
        System.out.println("m=" + m);//m=50
        m = 100;
        System.out.println("m=" + m);//m=100
    }
   '''參數為對象,不改變引用的值'''
   '''s即sMain指向的對象執行了append方法,在原來的字符串上加了段“_add”'''

    public static void add(StringBuffer s) {
        s.append("_add");
    }

    '''參數為對象,改變引用的值 '''
    '''引用變量指向了一個新的對象,已經不是sMain指向的對象了'''
    public static void changeRef(StringBuffer s) {
        s = new StringBuffer("Java");
    }

    public static void main(String[] args) {
        int i = 50;
        testBasicType(i);
        System.out.println(i);'''i=50'''
        StringBuffer sMain = new StringBuffer("init");
        System.out.println("sMain=" + sMain.toString());'''sMain=init'''
        add(sMain);
        System.out.println("sMain=" + sMain.toString());'''sMain=init_add'''
        changeRef(sMain);
        System.out.println("sMain=" + sMain.toString());'''sMain=Java'''
    }
}

看這里,給人的感覺是傳遞過來的明明是對象的引用,為什么就是值得傳遞呢? 
因為傳遞之前,被傳的就是個引用啊,我們所謂的“傳地址”,在傳之前,那可是一個實例,傳過來的是實例的地址。這里傳遞的值,從始至終就是個地址,sMain就是個地址,傳給s還是個地址。你們感受下:

'''參數為對象,不改變引用的值'''
'''s即sMain,指向的對象執行了append方法,在原來的字符串上加了段“_add”'''
    public static void add(StringBuffer s) {
        s.append("_add");
    }

以上輸出的結果會是“init_add”

而這里,s引用了一個新的對象,根本沒有進行參數的傳遞,它和之前的sMain沒有關系了。

'''參數為對象,改變引用的值 '''
'''引用變量指向了一個新的對象,已經不是sMain指向的對象了'''
    public static void changeRef(StringBuffer s) {
        s = new StringBuffer("Java");
    }

以上輸出的結果會是“Java” 

引用《Java編程思想》中的一段話:

倘若“將一個對象賦值給另一個對象”,實際是將“引用”從一個地方復制到另一個地方.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM