1、探究java方法參數傳遞——引用傳遞?值傳遞!


原創博文,轉載請注明出處。謝謝~~


 

 

java程序運行時,其對象是怎么進行放置和安排的呢?內存是怎么分配的呢?理解好這個很有好處!java有5個地方可以存儲數據:

1、寄存器。這是最快的存儲區,位於處理器內部。java程序員無法感知到它的存在,所以不用深究。

2、堆棧。位於內存中,通過堆棧指針可以獲得直接的內存分配。對象引用和基本數據(int,boolean等)存放於堆棧中。注意:是對象的引用,對象數據本身卻是存放在中的。於對象而言,在堆中存放的只是對象數據的一個地址(類似於C語言的指針),通過它可以訪問到對象本身的屬性和方法。

3、堆。同樣位於內存中,用於存放所有的java對象。需要一個對象的時候,只需要用new寫一行簡單的代碼,當執行這行代碼的時候,會直接在堆中進行空間分配。

4、常量存儲。常量值直接存放在代碼內部,保證安全。

5、非RAM存儲。比如,流對象和持久化對象,可以存放在磁盤文件中。

 

先來看一看下面這個對象是如何存放的

String s = new String("hello");

下圖表示了其存儲狀態:

new String("hello") 語句中用關鍵字 new 定義了一個String的對象,並為其在 “堆內存” 中分配了合適的存儲空間。

s 是一個String型對象的引用,實際上就是堆內存中一個String型對象的地址。引用 s 保存在 “堆棧” 中

 

清楚了對象和其引用的存儲方式以后,我們再來看看方法的參數傳遞是怎么一回事情。看看下面語句:

List-1:

1 public static void main(String[] args) {
2         
3     Func f = new Func();
4         
5     A a1 = new A();
6     f.modify(a1);
7 }

語句很簡單,在main方法中首先定義了一個Func對象f和A對象a1,然后將a1作為參數傳遞給f的modify方法中。程序在第6行的時候會從main方法跳轉到f.modify(...)方法內部執行。下圖展示了數據的保存狀態:

可以清楚的看到main方法向modify方法中傳遞的是對象引用 a1 的一份拷貝(為了方便區分,將拷貝命名為aa1),這兩個引用都指向同一塊堆內存區域(同一個A對象)。所以,我們可以想象,在modify方法中可以通過 aa1 來修改A對象的屬性,而且其變化在由modify返回main以后依然會保留下來

 

下面通過一個例子來分析一下:

List-2:

 1 package com;
 2 
 3 public class A {
 4     int cunt = 5;
 5     String str = "hello";
 6     
 7     public String toString() {
 8         return "A [cunt=" + cunt + ", str=" + str + "]";
 9     }
10 }
 1 package com;
 2 
 3 public class Func {
 4     
 5     public void modify(A a){
 6         //通過“對象引用”來修改對象的屬性
 7         a.cunt = 111;
 8         a.str = "modify";
 9     }
10     
11     public void newObj(A a){
12         //創建了一個新的對象,企圖在方法中將新對象引用賦值給形參a
13         //方式來達到修改外圍方法中實參引用的目的。 14         //注意:這個是做不到的。
15         a = new A();
16         a.cunt = 222;
17         a.str = "newObj";
18     }
19     
20     public A reNewObj(A a){
21         //返回一個新對象,在外圍方法中將返回的對象引用賦值給實參 22         //這種方法是可以實現的。
23         a = new A();
24         a.cunt = 333;
25         a.str = "reNewObj";
26         return a;
27     }
28     
29     public static void main(String[] args) {
30         
31         Func f = new Func();
32         
33         //在modify方法中通過形參來改變a1對象的屬性,在modify
34         //方法返回以后,修改的屬性可以保留下來
35         A a1 = new A();
36         f.modify(a1);
37         System.out.println(a1);
38         
39         //企圖通過newObj方法讓a2指向新的對象40         //但是,這樣做不可能達到目的
41         A a2 = new A();
42         f.newObj(a2);
43         System.out.println(a2);
44         
45         //reNewObj方法會返回一個新的對象引用,並將其賦值給a3 46         //這種方式可以讓a3指向一個新的對象。
47         A a3 = new A();
48         a3 = f.reNewObj(a3);
49         System.out.println(a3);
50         
51     }
52 }

運行結果:

我們知道,A對象初始化的時候 cunt = 5, str = "hello" 。現在來分析上面的三個結果的原理。

1、第一個結果是 “cunt = 111, str = "modify" 說明 f.modify(a1) 方法中通過形參a1對A對象屬性的修改被保留了下來。現在來分析,為什么會保留下來??

 

解釋:

①. 在main方法中,a1指向堆內存中的一個A對象,a1是作為A對象的一個引用,其值是“引用”(對空間的地址值)。

調用f.modify(a1)的時候,a1作為實參傳遞給modify方法,由於java參數傳遞是屬於“值傳遞”(值的拷貝),所以在modify堆棧中會有a1的一份拷貝,兩個a1指向同一個A對象。

②. 通過modify堆棧中的引用a1可以操作其所指向的對內存中的對象。在modify方法中將堆內存中的A對象屬性值修改為“cunt = 111, str = "modify"”。

③. modify方法返回以后,modify堆棧中的a1引用所占內存被回收,main堆棧中的a1依然指向最開始的那個對象空間。由於在modify方法中已經將對象的屬性修改了,所以在main中的a1可以看見被修改后的屬性值。

 

2、第二個結果是 “cunt = 5, str = "hello",說明main方法中的a2所指向的對象沒有變化。42行中 f.newObj(a2); 方法的原本意圖是:在newObj方法中新創建一個A對象,並將其賦值給newObj的形參a2,並且期望在newObj方法返回以后main方法的局部變量a2也指向新的A對象。

可是,從打印的結果來看,main方法中的局部變量a2依然指向原來的A對象,並沒有指向newObj方法中新建的A對象。

那么,這個是為什么呢??

解釋:

①. 前面兩個圖和1中的解釋是一樣的,無非就是一個參數的“值傳遞問題”。

②. 在newObj方法中有這樣的三條語句:a = new A(); a.cunt = 222; a.str = "newObj";

   其執行過程是這樣的:在堆內存中開辟一塊新的A對象空間,讓newObj堆棧中的a2引用指向新的對象空間 → 后兩句語句會修改新對象的屬性值。

     注意,此時main堆棧中的a2依然指向最開始的那個對象空間,而newObj堆棧中的a2卻指向了新的對象。

③. newObj方法返回,newObj堆棧空間被回收,堆空間中的新對象沒有任何變量引用它,在合適的時機會被JVM回收。此時,返回到main方法中,此時的a2依然指向

依然指向最開始的那個A對象,其屬性值沒有發生變化。

 

所以,外圍方法中的實參無法看見在內部方法中新創建的對象。除非,新創建的對象作為內部方法的返回值,並且在外圍方法中將其賦值給實參變量。就像47~49行所做的那樣:在reNewObj方法中新創建一個對象並賦值給形參,同時在方法最后將新對象作為返回值返回給外圍方法,進一步在外圍方法中將返回的對象賦值為a3,從而達到目的。

 


免責聲明!

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



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