1. 程式人生 > >Java 函式呼叫是傳值還是傳引用? 從位元組碼角度來看看!

Java 函式呼叫是傳值還是傳引用? 從位元組碼角度來看看!

本文是網友KailunTalk文章的精簡版, 他從位元組碼的角度解釋了Java 函式呼叫中傳遞引數的方式, 充分體現了一個程式設計師深度挖掘,瞭解底層的精神, 原文地址:https://my.oschina.net/kailuncen/blog/915043

1傳值還是傳地址?

  少廢話,先看程式碼:

  

  function1會把車的顏色改為blue

  function2 建立了一個新的黑色的車car2, 並且把新車賦值給了輸入引數car。

  繼續看測試類TestReference

  

  經過process.function1的處理後,輸出結果是:

  

  這是很容易理解的, 車的顏色從red被改成了blue。

  如果修改一下TestReference, 讓它去呼叫process.function2(car) , 會有什麼效果呢? 有經驗的程式設計師可能立刻就能給出答案:

  

  在main函式中的那個紅色的車根本沒有受到影響。

  為什麼會這樣呢? 其實在Java函式呼叫的過程當中,對於物件型別的引數,Java傳遞的是這個物件引用的copy, 這個引用的copy和原引用都指向堆上的同一個物件。

  在function1中, 雖然使用的是原有引用的copy,但是操作的卻是堆中的物件, 於是把這個顏色值改成了blue .

  在function2中把這個copy指向了新物件 car2, 那main函式中原有的引用呢? 還是指向堆中的老的物件, 所以沒有改變。

2如何實現函式呼叫

  理解到這裡,一般來說就夠了,但是對於一個刨根問底人,肯定要繼續挖掘一下,深入到位元組碼層次去看看。

  首先得理解一下JVM是怎麼實現函式呼叫的, 其實也很簡單,JVM把每個函式都封裝成一個叫做“幀(Frame)”的東西, 在這個Frame當中,最重要的兩個東西就是區域性變量表和運算元棧。

  

  Java 的計算都是基於棧, 在函式執行過程中會不停地入棧、出棧,計算。 有些中間結果和區域性變數就會暫時存放到區域性變量表中。

  那當main函式呼叫function2的時候會是什麼狀況呢?

  首先,main函式的Frame 會作為一個元素被壓入JVM的棧中(又是棧! 所以函式幀通常稱為棧幀

), function2的棧幀也會作為一個元素壓入棧中:

  

  執行完function2 以後,它的棧幀就會退出,接著執行main函式。

3深入位元組碼

  接下來就有難度了,需要深入位元組碼了。

  先使用javap -verbose TestReference.class , 在輸出的結果中能看到main函式的各種資訊:

  

(點選看大圖)

  雖然很長,但每行位元組碼後面有註釋, 能大概看個明白。

  第0行:建立Process物件

  第7行:astore_1 ,把process物件的引用放到了索引為1的區域性變量表中

  第8-14行:建立了Car 物件(顏色為red)

  第17行:astore_2, 把car物件的引用放到了索引為2的區域性變量表中

  第25行: aload_1, 把區域性變量表中索引為1的物件引用(process物件)放到了運算元棧的棧頂

  第26行: aload_2, 又把區域性變量表中索引為2的物件引用(car 物件)放到了運算元棧的棧頂

  (備註:上面說第x行是為了方便,其實是不正確的,正確的說法是偏移量)

  到第26行為止,main函式棧幀是這樣的:

  

  圖中的 prcess ref , car ref 表示對兩個物件的引用

  可能有人要問了,為什麼要把car ref, process ref 放到棧頂呢?

  還是那句話,java 是基於棧來執行的,由於function2需要兩個引數,一個是this(就是process ref) ,一個是car (car ref) ,所以需要把他們兩個傢伙放到棧頂,形成新棧幀的時候會把他倆傳遞過去, 並且把這個兩個值從棧中清除。

  關鍵的一步來了, 第27行,呼叫function2!

  Java函式棧變成這個樣子了:

  

  注意區域性變量表, 從main中複製過來兩個值,一個是this(process ref) 另外一個就是 car ref。

  main 的frame還在,只是我們沒畫出來。

  再看看function2的程式碼:

  

  第0-6 行: 建立了一個新的car 物件(顏色為black), 暫時記做car2 ref

  第9行: astore_2 , 把car2 ref 存放到了區域性變量表的索引為2的位置

  第10行:aload_2, 把car 2 ref 放到了棧頂

  第11行: astore_1 , 關鍵的一行, 把 car2 ref 放到了索引為1的位置, 相當於把傳遞進來的引數car ref 給覆蓋掉了

  

  現在請問: 在main 棧幀區域性變量表中的 car ref受到影響了嗎?

  答案肯定是:沒有!

  當function2 執行完, 從JVM棧中退出,接著執行main frame:

  

  car ref 所指向的仍然那個有著red 顏色的物件 , 沒有任何變化 !

  這篇文章沒有涉及基本型別,實際上也是類似的, 總結一下就是:Java 的引數傳遞,是通過Copy引數的值來進行的,這個值可能是一個基本型別, 也可能是一個引用。