java的值傳遞和引用傳遞


首先:吐槽一波,對於改傳進來的參數的做法真的很s 。
所以 來了解一波 java的值傳遞和引用傳遞的問題。

參數

  1. 形參 方法被調用時需要傳遞進來的參數,如:func(int a)中的a,它只有在func被調用期間a纔有意義,也就是會被分配內存空間,在方法func執行完成後,a就會被銷燬釋放空間,也就是不存在了。
  2. 實參 方法被調用時是傳入的實際值,它在方法被調用前就已經被初始化並且在方法被調用時傳入。

java數據類型

  1. 基本類型 也就是我們常用的一些八種數據類型。
  2. 引用類型 在句柄中存放着實際內容所在地址的地址值的一種數據形式。主要包括:類、接口、數組。

java內存劃分

  1. 虛擬機棧
  2. 程序計數器
  3. 方法區
  4. 本地方法棧

java虛擬機棧

虛擬機棧由若干個棧幀組成。
虛擬機棧是Java方法執行的內存模型,棧中存放着棧幀,每個棧幀分別對應一個被調用的方法,方法的調用過程對應棧幀在虛擬機中入棧到出棧的過程。
棧是線程私有的,也就是線程之間的棧是隔離的;當程序中某個線程開始執行一個方法時就會相應的創建一個棧幀並且入棧(位於棧頂),在方法結束後,棧幀出棧。
棧幀 :是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧的棧元素。
每個棧幀中包括:

  • 局部變量表 :用來存儲方法中的局部變量(非靜態變量、函數形參)。當變量爲基本數據類型時,直接存儲值,當變量爲引用類型時,存儲的是指向具體對象的引用。
  • 操作數棧:JAVA虛擬機的解釋執行引擎被稱爲“基於棧的執行引擎”,其中所指的棧就是指操作數棧。
  • 指向運行時常量池的引用 :存儲程序執行時可能用到常量的引用。
  • 方法返回地址:存儲方法執行完成後的返回地址。

堆是用來存儲對象本身和數組的,在JVM中只有一個堆,因此,堆是被所有線程共享的。

方法區

方法區是一塊所有線程共享的內存邏輯區域,在JVM中只有一個方法區,用來存儲一些線程可共享的內容,它是線程安全的,多個線程同時訪問方法區中同一個內容時,只能有一個線程裝載該數據,其它線程只能等待。
方法區可存儲的內容有:類的全路徑名、類的直接超類的權全限定名、類的訪問修飾符、類的類型(類或接口)、類的直接接口全限定名的有序列表、常量池(字段,方法信息,靜態變量,類型引用(class))等。

本地方法棧

本地方法棧的功能和虛擬機棧是基本一致的,並且也是線程私有的,它們的區別在於虛擬機棧是爲執行Java方法服務的,而本地方法棧是爲執行本地方法服務的。

程序計數器

線程私有的。
記錄着當前線程所執行的字節碼的行號指示器,在程序運行過程中,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、異常處理、線程恢復等基礎功能都需要依賴計數器完成。

數據的存儲

基本數據類型的存儲

  1. 基本數據類型的局部變量 定義基本數據類型的局部變量以及數據都是直接存儲在內存中的棧上,也就是前面說到的 “虛擬機棧” ,數據本身的值就是存儲在棧空間裏面。
    當我們寫“int age=50;”,其實是分爲兩步的:首先JVM創建一個名爲age的變量,存於局部變量表中,然後去棧中查找是否存在有字面量值爲50的內容,如果有就直接把age指向這個地址,如果沒有,JVM會在棧中開闢一塊空間來存儲“50”這個內容,並且把age指向這個地址。因此我們可以知道:
    我們聲明並初始化基本數據類型的局部變量時,變量名以及字面量值都是存儲在棧中,而且是真實的內容。
    我們再來看“int weight=50;”,按照剛纔的思路:字面量爲50的內容在棧中已經存在,因此weight是直接指向這個地址的。由此可見: 棧中的數據在當前線程下是共享的 。
    當代碼中重新給weight變量進行賦值時,JVM會去棧中尋找字面量爲40的內容,發現沒有,就會開闢一塊內存空間存儲40這個內容,並且把weight指向這個地址。由此可知:

基本數據類型的數據本身是不會改變的,當局部變量重新賦值時,並不是在內存中改變字面量內容,而是重新在棧中尋找已存在的相同的數據,若棧中不存在,則重新開闢內存存新數據,並且把要重新賦值的局部變量的引用指向新數據所在地址。

  1. 基本數據類型的成員變量成員變量:顧名思義,就是在類體中定義的變量。 基本數據類型的成員變量名和值都存儲於堆中,其生命週期和對象的是一致的。
  2. 基本數據類型的靜態變量 基本數據類型的靜態變量名以及值存儲於方法區的運行時常量池中,靜態變量隨類加載而加載,隨類消失而消失

引用數據類型的存儲

堆是用來存儲對象本身和數組,而引用(句柄)存放的是實際內容的地址值。
因此 Person per = newPerson();實際上有兩個過程:一個是 Person per定義變量。 per = newPerson()給變量賦值。
在執行Person per;時,JVM先在虛擬機棧中的變量表中開闢一塊內存存放per變量,在執行per=new Person()時,JVM會創建一個Person類的實例對象並在堆中開闢一塊內存存儲這個實例,同時把實例的地址值賦值給per變量。因此可見:
對於引用數據類型的對象/數組,變量名存在棧中,變量值存儲的是對象的地址,並不是對象的實際內容。

值傳遞和引用傳遞

值傳遞:
在方法被調用時,實參通過形參把它的內容副本傳入方法內部,此時形參接收到的內容是實參值的一個拷貝,因此在方法內對形參的任何操作,都僅僅是對這個副本的操作,不影響原始值的內容。
值傳遞傳遞的是真實內容的一個副本,對副本的操作不影響原內容,也就是形參怎麼變化,不會影響實參對應的內容。

引用傳遞:
”引用”也就是指向真實內容的地址值,在方法調用時,實參的地址通過方法調用被傳遞給相應的形參,在方法體內,形參和實參指向同一塊內存地址,對形參的操作會影響的真實內容。

總結

如果是對基本數據類型的數據進行操作,由於原始內容和副本都是存儲實際值,並且是在不同的棧區,因此形參的操作,不影響原始內容。

如果是對引用類型的數據進行操作,分兩種情況,一種是形參和實參保持指向同一個對象地址,則形參的操作,會影響實參指向的對象的內容。一種是形參被改動指向新的對象地址(如重新賦值引用),則形參的操作,不會影響實參指向的對象的內容。

參考文章:
link

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章