Java中類,對象,方法的內存分配

如下針對引用數據類型:
在內存中,類是靜態的概念,它存在於內存中的CodeSegment中。
當咱們使用new關鍵字生成對象時,JVM根據類的代碼,去堆內存中開闢一塊控件,存放該對象,該對象擁有一些屬性,擁有一些方法。可是同一個類的對象和對象之間並非沒有聯繫的,看下面的例子:數組

class Student{
    static String schoolName;
    String name;
    int age;函數

    void speak(String name){
        System.out.println(name);
    }佈局

    void read(){
    }
}this

class Test{
    public statis void main(String[] args){
        Student a = new Student();
        Student b = new Student();
    }
}spa

在上面的例子中,生成了兩個Student對象,兩個對象擁有各自的name和age屬性,而schoolName屬性因爲是static的,被全部的對象所共有,並且每一個對象都有權更改這個屬性。
而對於方法speak()和read()來說,它們被全部的對象所共有,但並非說哪一個對象擁有這些方法。方法僅僅是一種邏輯片斷而已,它不是實物,嚴格來說不能被擁有。
方法存在的意義就是對對象進行修改。(個人理解)
上述speak()方法,雖然兩個對象都擁有相同的方法,可是因爲其操做的對象不一樣,因此執行起來的效果也不一樣。再說一次,方法是看不見摸不着的,它僅僅是一種邏輯操做!只有看成用於具體的對象時,方法才具體化了!
方法在不執行的時候不佔用內存空間,只有在執行的時候纔會佔用內存空間。
就比如說一我的會翻跟斗,他翻跟斗的時候是須要空間的,可是他不翻跟斗的時候是不須要額外的空間的。可是無論他翻不翻跟斗,他始終是具備翻跟斗的技能的

Java中的內存佈局(其餘面向對象的語言也是如此)code

Java中的內存空間分爲四種:
1. code segment(代碼段,是否是所謂的方法區?)
存儲class文件的內容,即咱們寫的代碼。
2. data segment(數據段)
存儲靜態變量
3. heap segment
堆空間,存儲new出來的對象
4. stack segment
棧空間,存儲引用類型的引用(注意,這裏存儲的不必定是對象所處的物理地址,可是必定可以根據這個內容在堆中找到對應的對象),局部變量和方法執行時的方法的代碼對象

以上面的speak(String name)方法的調用爲例來分析下內存:
調用該方法時,首先在棧控件開闢了一塊區域存放name引用。而後將傳入的那個對象的「地址」賦值給這個引用。因而出現了什麼狀況?兩個引用指向同一個對象。而咱們操做對象時是經過對象的引用來執行操做,因此當一個對象有一個以上的引用同時指向它時,就會出現一些比較混亂的事情了。內存

經過馬士兵在課上講的一個小例子來看看:get

public class Test {class

    public static void main(String[] args) {
        Test test = new Test();
        int data = 10;
        BirthDate b1 = new BirthDate(5,4,1993);
        BirthDate b2 = new BirthDate(25,5,1992);

        test.change(data);
        test.change1(b1);
        test.change2(b2);
        System.out.println(data);
        b1.display();
        b2.display();
    }

    void change(int i){
        i = 100;
    }

    void change1(BirthDate b){
        b = new BirthDate(1,1,1);
    }

    void change2(BirthDate b){
        b.setDay(100);
    }
}

class BirthDate{
    int day;
    int month;
    int year;

    BirthDate(int day,int month,int year){
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return day;
    }
    public void setDay(int day) {
        this.day = day;
    }
    public int getMonth() {
        return month;
    }
    public void setMonth(int month) {
        this.month = month;
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }

    public void display(){
        System.out.println("day = "+day+"\nmonth = "+month+"\nyear = "+year);
    }
}

    輸出爲:

    10
    day = 5
    month = 4
    year = 1993
    day = 100
    month = 5
    year = 1992

從輸出結果來看看發生了什麼:
1. 調用change(int i)
此時在棧空間中新建了一個i,並把data的值複製給i。這個時候在棧空間中有兩個int類型的數,兩者的值雖然都爲10,可是兩者毫無關係,棧空間中有兩個10.
在方法體中對i進行賦值,該操做是對i進行的,並不影響data,因此當方法結束時data仍是原來的data,連地址都沒變一下。同時在方法結束時i自動從棧空間中消失。
2. 調用change1(BirthDate b)
此時在棧空間中新建一個引用b,在調用該方法的時候,將傳進來的引用的值複製給b,即b和b1擁有相同的內容,指向同一個對象。在方法體中對b又進行賦值操做,首先在堆空間中new出一個新的對象,而後將b改成指向這個新的對象。該操做也未影響b1。方法結束後,引用b消失,剛纔new出來的新對象成了垃圾,等待GC的回收。
3. 調用change2(BirthDate b)
同上,先在對中新建一個引用,名爲b,而後將其指向b2所指向的對象。注意,此時調用了該對象的方法,修改了部分屬性,因此此操做改變了這個對象,而b2也指向這個對象,因此最後b2的輸出發生了變化。

從上面的三個方法能夠看出,當方法中的參數列表不爲空時:
- 若是參數是引用數據類型,該方法執行的過程首先是建立若干個引用,而後將這些引用的值和傳進來的引用的值一一對應複製。複製完以後,傳進來的參數(引用)的工做也就完成了。
- 若是參數是基本數據類型,那麼首先在棧中建立對應數量個變量,將這些變量的值和傳進來的參數一一對應複製。複製完以後也沒有外面什麼事了。

綜上:傳參時要注意,若是對傳進去的參數(其實是引用)進行了從新的賦值操做,那麼該方法應該有一個返回值,不然該方法是沒有意義的,如同上面的change1()。

    方法通常有兩個做用:1. 對某變量進行改變。 2. 根據傳進來的參數返回另外一變量。
    若是一個方法不想有返回值,只是想對某變量進行改變,不要將該對象做爲參數傳進去,而直接在方法中得到其訪問權限而後直接更改。方法的參數列表爲空。
    若是一個方法要有返回值,最好先在方法內部new一個臨時變量,先將傳進來的參數複製一下,邏輯執行完後,把臨時變量return出去。

20170620更新:
其實以上問題涉及到的東西是值傳遞與引用傳遞。在C++中兩者都有,可是在Java中只有值傳遞。具體到實踐中分兩種狀況:
- 傳遞的是基本數據類型:
其實傳遞的是值的拷貝。在方法中對值進行操做,並不影響傳進去的那個值。如上面的change()方法,傳值進去時只是按照data的樣子從新建立了一個i,本質上data和i除了值相同之外,是兩個獨立的個體。

    傳遞的是數組對象或者其餘對象:
    實際上傳遞的是對象的引用,可是並非把引用傳過去,而是把引用複製過去。就像上面的change1()方法同樣,其本質是將傳參b1這個引用的值複製給引用b。b1和b除了值相同外,是兩個獨立的個體。可是因爲兩者值相同,因此指向了堆內存中的同一個對象,兩者均可以用來操做對象。

總結一下:
傳值,傳的都是棧中所儲存的東西的拷貝。若是傳進去的東西是基本數據類型,那麼就直接複製一份,對其操做不影響原來的數據。
若是傳進去的是一個引用,那麼其實也是複製一份,因此指向同一對象。當操做這個引用時,改變了這個引用所指向的對象,看起來會讓人以爲當時傳進去的是對象自己,否則怎麼在方法中對其修改會改變本來的對象呢?其實這是個假象。時刻記住,傳進函數的都是棧內存中的東西,堆內存的東西是不會被傳進去的。而函數內部能不能改變原來對象的值,就要看你是否是保持了原來傳進去的引用所指向的對象沒變。

PS:才疏學淺,若有錯誤請指出,謝謝!  

相關文章
相關標籤/搜索