天天看點

一篇文章寫明白Java的值傳遞

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

先從一道面試題說起:

一篇文章寫明白Java的值傳遞
一篇文章寫明白Java的值傳遞

檢視答案:

一篇文章寫明白Java的值傳遞

值與引用

為了糾正值傳遞和引用傳遞的一些誤解,此處探讨的并不是值類型和引用類型,而是指派操作時對各部分的名稱。

以上面面試題為例,有 Point p1 = new Point(0, 0);

一篇文章寫明白Java的值傳遞

變量p1裡存儲着實際對象的位址,一般稱這種變量為"引用",引用指向實際對象,我們稱實際對象為該引用的值;指派操作符=實際上做的就是将引用指向值的位址的工作,如果我們有p1 = new Point(3,3); 的話,情形就是這樣:

一篇文章寫明白Java的值傳遞

我們要注意到,在堆中的對象Print(0,0) 并沒有發生改變,改變的隻有引用p1 指向的位址。

值與引用就是以上這些,而值傳遞和引用傳遞,和這些一點關系也沒有。

值傳遞和引用傳遞

我們可以找到網上資料對值傳遞的定義,大多是這樣的:

在方法調用時,傳入方法内部的是實參引用的拷貝,是以對形參的任何操作都不會影響到實參。

這句話本身對值傳遞的定義是比較準确的,但由于前後的沖突和概念的盲區,讓我們了解起來産生了歧義,概況起來歧義有三:

既然傳遞的是引用,那麼應該是引用傳遞才對,為什麼叫值傳遞;

引用本身也是值,本質就是個指針,是以所有傳遞都是值傳遞;

大部分會被上述概念的後半句“形參的任何操作都不會影響到實參”誤導,因為我們很容易就可以做到使用形參改變實參,比如上述面試題中的p1.setLocation(5, 5);或下面代碼:

一篇文章寫明白Java的值傳遞

很多時候我們對值傳遞有疑惑,本質原因是沒有弄明白值傳遞和引用傳遞到底是在描述什麼,我們錯以為它們的名字是自解釋的:傳遞的是值就是值傳遞,傳遞的是引用就是引用傳遞,Java是值傳遞意味着java在方法調用時傳遞的是值。這就是我們上面說的概念盲區,對值傳遞和引用轉遞的正确解釋其實是這樣的:

值傳遞(Call by value)和引用傳遞(Call by reference),描述的是函數調用時參數的求值政策(Evaluation strategy),是對調用函數時,求值和取值方式的描述,而非傳遞的内容。

值與引用描述了兩種記憶體配置設定方式,值在堆上配置設定,引用在棧上配置設定(在這裡要區分值類型和引用類型)。而值傳遞和引用傳遞描述的則是參數求值政策,兩者之間不存在任何依賴關系。

綜上,我們可以得出Java是值傳遞這種說法也是不準确的,完整的說法應該是 java在函數調用時采用的求值政策為值傳遞。 嚴格意義上講,求值政策也不僅僅有值傳遞和引用傳遞,還有共享對象傳遞(Call by sharing)和值-傳回傳遞(Call by copy-restore),但這些概念對于我們了解Java值傳遞并無益處。

理清了值傳遞和引用傳遞所描述的具體内容,我們現在來看關于Java值傳遞概念的第三點歧義:形參的任何操作都不會影響到實參。以上述colorList 代碼為例,我們已經知道值傳遞這種求值政策在函數調用時會把實參引用的拷貝傳入函數内,是以在調用removeFirst() 方法時有:

一篇文章寫明白Java的值傳遞

形參colorList'作為實參colorList的拷貝,它們寄存着一樣的位址,都指向堆中的同一個執行個體ColorList [BLUE, RED, GRAY] 。

我們在removeFirst() 方法内操作的是形參colorList'。但是剛剛說到,無論是形參還是實參,它們都隻會存儲執行個體對象的位址,真正的對象仍是堆中它們共同指向的ColorList 。而我們在removeFirst() 方法内調用的remove() ,是執行個體對象ColorList 本身提供的可以改變自身的函數。是以,當removeFirst() 方法内執行colorList'.remove(0) 後,形參colorList' 所指向的執行個體對象ColorList 就變成了ColorList[RED, GRAY] ,而實參colorList仍然指向了這個執行個體對象,是以當方法調用完成後,執行個體對象改變了。

這樣看來,關于“形參的任何操作都不會影響到實參”确實是不嚴謹的,那麼這句話在什麼情況下生效呢,我們将removeFirst() 方法改成如下代碼:

一篇文章寫明白Java的值傳遞

在這個新的removeFirst() 方法中有:

一篇文章寫明白Java的值傳遞

按照值傳遞的求值政策規定,傳入實參引用的拷貝這一步沒變。在“值與引用”小節中我們說到 指派操作符的實際工作是将引用指向值,在這個removeFirst() 方法中我們将形參colorList' 的引用做了重指派操作,是以現在它指向了一個新的執行個體ColorList [ RED, GRAY] ,但是随着removeFirst() 方法執行完畢後退出,形參colorList'也會被回收,而實參colorList指向的ColorList [BLUE, RED, GRAY] 并沒有發生改變(變的是形參指向的執行個體,但是在方法退出後,方法參數colorList' 就會被回收,而它指向的執行個體也就會變成垃圾對象)。

關于值傳遞概念的三點歧義就解釋完畢了,我們在這裡可以給出java值傳遞一個正确完整的概念了:

java 使用的是一種名為值傳遞的求值政策,這種政策在傳值過程中會複制實參的引用,并将這份拷貝傳入到方法形參中,是以對形參的任何重指派操作都不會對實參産生影響。

在下一小節中我們會解析開篇的面試題,如果你對Java值傳遞仍然了解不好,我會在下一節中分享一個技巧幫助你更徹底的弄明白Java值傳遞。

面試題解析

回到開篇的面試題,在modifyPoint() 方法中,頭三行代碼使用了一個臨時變量tmpPoint 互換了形參p1 和p2 的值,是以在方法内部,形參p1 實際上指向了實參p2 ,形參p2 指向了實參p1 :

一篇文章寫明白Java的值傳遞

關于Java的值傳遞政策,有一種比較取巧的了解,就是完全可以把函數的傳參過程等同于指派操作符=來了解,我們在調用modifyPoint(p1,p2)時,可以了解在方法内部有形參p1'=實參p1,形參p2'=實參p2,這樣我們在方法裡操作的p1'和p2'實際隻是個臨時變量,它的生命周期僅限于方法裡。

在形參p1' 與形參p2' 互換後,堆棧資訊簡易表示為:

一篇文章寫明白Java的值傳遞

然後調用了p1'.setLocation(5, 5); 我們在上節說過,如果執行個體對象本身提供了改變自身的方法,那麼在形參調用該方法後也會改變實參的,因為它們都指向了同一個執行個體,是以這時實參p2 也變為Point[5.5] 。

代碼走到下一行p2' = new Point(5, 5); 基礎整篇文章的論述,這一行我們可以直接跳過不管了,因為形參的重指派操作不會影響到實參。最後的堆棧資訊如下:

一篇文章寫明白Java的值傳遞

是以最終答案顯而易見:[0,0],[5,5]

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/zhibo

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-04-12

本文作者:luckykelan

本文來自:“

掘金

”,了解相關資訊可以關注“掘金”