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


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

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

 1 class Student{
 2     static String schoolName;
 3     String name;
 4     int age;
 5 
 6     void speak(String name){
 7         System.out.println(name);
 8     }
 9 
10     void read(){
11     }
12 }
13 class Test{
14     public statis void main(String[] args){
15         Student a = new Student();
16         Student b = new Student();
17     }
18 }
View Code

 

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

Java中的內存布局(其他面向對象的語言也是如此)

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

以上面的speak(String name)方法的調用為例來分析下內存: 
調用該方法時,首先在棧控件開辟了一塊區域存放name引用。然后將傳入的那個對象的“地址”賦值給這個引用。於是出現了什么情況?兩個引用指向同一個對象。而我們操作對象時是通過對象的引用來執行操作,所以當一個對象有一個以上的引用同時指向它時,就會出現一些比較混亂的事情了。

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

  

 1 public class Test {
 2  
 3     public static void main(String[] args) {
 4         Test test = new Test();
 5         int data = 10;
 6         BirthDate b1 = new BirthDate(5,4,1993);
 7         BirthDate b2 = new BirthDate(25,5,1992);
 8  
 9         test.change(data);
10         test.change1(b1);
11         test.change2(b2);
12         System.out.println(data);
13         b1.display();
14         b2.display();
15     }
16  
17     void change(int i){
18         i = 100;
19     }
20  
21     void change1(BirthDate b){
22         b = new BirthDate(1,1,1);
23     }
24  
25     void change2(BirthDate b){
26         b.setDay(100);
27     }
28 }
29  
30 class BirthDate{
31     int day;
32     int month;
33     int year;
34  
35     BirthDate(int day,int month,int year){
36         this.day = day;
37         this.month = month;
38         this.year = year;
39     }
40  
41     public int getDay() {
42         return day;
43     }
44     public void setDay(int day) {
45         this.day = day;
46     }
47     public int getMonth() {
48         return month;
49     }
50     public void setMonth(int month) {
51         this.month = month;
52     }
53     public int getYear() {
54         return year;
55     }
56     public void setYear(int year) {
57         this.year = year;
58     }
59  
60     public void display(){
61         System.out.println("day = "+day+"\nmonth = "+month+"\nyear = "+year);
62     }
63 }
View Code

 

輸出為:

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:才疏學淺,如有錯誤請指出,謝謝!


免責聲明!

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



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