java 傳值 vs. 傳引用java程序的函數(shù)調(diào)用到底是傳值呢還是傳參呢?這可是個難纏的問題,如果搞不清楚還是挺容易出錯的:P對于這個問題,最經(jīng)典的解釋莫過于“java函數(shù)是傳值的,java函數(shù)傳遞的參數(shù)是對象的引用”
這兩句話好像初聽上去有點繞,不過意思倒是表達得蠻精確的。 我看到過幾個解釋這個問題的例子,不過個人感覺看過例子之后還是只知道是什么不知道為什么,停留在照貓畫虎的水平上還是挺容易出問題的。所以舉例子之前,先從jvm的實現(xiàn)原理上有個了解應當是不無裨益的。jvm的結構圖前一陣子貼到blog上了,那可是從“深入java虛擬機”這本巨牛的書上看來的,絕對有權威性。從jvm的結構圖上可以看出來,jvm在實現(xiàn)的時候將屬于它的內(nèi)存分為五部分,其中程序代碼(嚴格的說應當是字節(jié)碼)是放在java棧的棧幀中,而對象是從堆中分配的,堆這個東西我看可以理解成“對象池”。程序和程序中需要用到的對象放在兩個相對獨立的區(qū)域中,那么程序怎么使用對象呢?答案是程序中真正使用對象的地方其實只是聲明了一個對象的引用,也就是把堆中分配了的相應對象的地址放到引用中,棧和堆之間就是通過一個一個的引用來聯(lián)系的。引用嘛,我理解就是一個指針常量,指針常量又是個什么東西呢?說白了,就是一個無符號整數(shù),這個整數(shù)所表達的是引用對象的地址。好了,這下清楚了,不管是基本類型變量(int,float,double什么的)還是對象,相應的內(nèi)存地址中存放的都是一個數(shù)(無符號整數(shù),整數(shù),浮點數(shù)等)。傳遞參數(shù)的時候傳遞的就是相應內(nèi)存地址中的數(shù),所以說“java函數(shù)是傳值的”。當然,這個數(shù)對于基本類型和對象類型來說意義是不一樣的,對于基本類型這個數(shù)就是其值本身,傳遞值的結果就是,改變新的變量的值不影響舊的變量的值;而對于對象來說這個數(shù)是它的地址,傳遞這個值就相當于傳遞了真實對象的引用,傳遞了引用或者說是地址的結果就是變化會全局可見,所以又可以說“java函數(shù)傳遞的參數(shù)是對象的引用”。
唔,松口氣啦。經(jīng)過上面這一小堆討論,不難理解為什么java在傳遞參數(shù)時對于基本類型和對象表現(xiàn)不同:)
現(xiàn)在開始舉例了,舉網(wǎng)上搜來的例子,看看是不是比原來沒有上面的解釋的時候好理解一點?
public class TestRef
{ public static void main(String[] args) { ValueObject vo1 = new ValueObject("A", 1); System.out.println("after vo1: " + vo1.getName()); //=A changeValue1(vo1); System.out.println("after changeValue1: " + vo1.getName()); //=A1, changed changeValue2(vo1); System.out.println("after changeValue2: " + vo1.getName()); //=A1, changeValue2內(nèi)部的賦值不會影響這里。 } /** * 使用vo1自身的函數(shù)對其內(nèi)部數(shù)據(jù)進行改變是有效的,函數(shù)外可反映出來, * 因為這是對對象本身的操作
* 這種object稱為可變的(mutable) * @param vo1 */ private static void changeValue1(ValueObject vo1) { vo1.setName("A1"); } /** * 在函數(shù)內(nèi)給vo1重新賦值不會改變函數(shù)外的原始值,因為這種改變了引用的指向 * @param vo1 */ private static void changeValue2(ValueObject vo1) { vo1 = new ValueObject("B", 2); System.out.println("inside changeValue2: "+ vo1.getName()); //=B,賦值操作引起的結果變化僅在changeValue2內(nèi)部有效 } } class ValueObject { public ValueObject() {} public ValueObject(String name, int id) { this.name = name; this.id = id; } private String name; private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } java中對象的每個實例(就是對象)內(nèi)存地址是唯一的,它一旦被創(chuàng)建,能夠對這個地址進行操作的就是其本身,如果ValueObject類中沒有public void setName之類的方法對這個類的實例中的數(shù)據(jù)進行修改的話,程序是沒有任何別的方法可以修改ValueObject類的實例中的數(shù)據(jù),這個就是java的封裝特性。對于不提供修改內(nèi)部數(shù)據(jù)的方法的類,我們稱為不可變(immutable)的類。在函數(shù)中對傳入的參數(shù)變量進行賦值操作,只能在函數(shù)范圍內(nèi)改變局部變量指向的引用地址,但是不會改變原始地址的內(nèi)容。因此,在changeValue2(...)函數(shù)內(nèi)部的vo1和函數(shù)外的vo1雖然名字相同,但是實際上是不同的實例變量,只不過指向了和函數(shù)外的vo1同樣的地址,所以當我們用vo1=... 對其進行賦值的時候,只不過是把函數(shù)內(nèi)的臨時變量指向了新的地址,并沒有改變原始vo1內(nèi)存地址中的內(nèi)容。這就是在運行changeValue2(...)之后,vo1的值在main范圍內(nèi)仍然沒有被修改的原因。而changeValue1里面是調(diào)用的ValueObject本身的function來更改其內(nèi)容,因此是原始內(nèi)存地址中的數(shù)據(jù)被更改了,所以是全局有效的. 總結:
對于引用類型的傳參也是傳值的,傳的是引用類型的值,其實就是對象的地址。 1. java參數(shù)傳遞值的。 2. java所有對像變量都是對像的引用。 不知道我解釋清楚沒,要是還是不能理解java參數(shù)傳遞的原理,強烈推薦看看“深入java虛擬機”一書的第五章,中文翻譯的不是特別出色,可以看看英文原版,應該比中文版好懂:) |
|