Java的call by value_call by value or reference ?
Java中參數傳遞是傳值還是傳引用呢?很多人遇到這個問題都會馬上給你拋出這個例子:
對方不想和你說話并向你拋了一段代碼
class Entry{
Integer value;
public Entry(Integer v){
this.value = v;
}
@Override
public String toString() {
return "Entry[value=" + value + "]";
}
}
public class CallByDemo{
public static void swap(int a,int b){
int temp = a;
a = b;
b = a;
}
public static void swap(Entry e1,Entry e2){
Integer temp = e1.value;
e1.value = e2.value;
e2.value = temp;
}
public static void main(String[] args) {
int a = 1;
int b = 2;
System.out.println("before:a="+a+",b="+b);
swap(a,b);
System.out.println("after :a="+a+",b="+b);
Entry e1 = new Entry(new Integer(1000));
Entry e2 = new Entry(new Integer(2000));
System.out.println("before:e1="+e1+"e2="+e2);
swap(e1,e2);
System.out.println("after :e1="+e1+"e2="+e2);
}
}
運行結果:
before:a=1,b=2
after :a=1,b=2
before:e1=Entry[value=1000]e2=Entry[value=2000]
after : e1=Entry [value=2000 ]e2=Entry[value=1000]
然后言之鑿鑿地拋出這個結論:
當參數為基本類型時為傳值
當參數為對象引用類型為傳引用
好像沒有毛病啊,但是如果我把swap(Entry e1,Entry e2)改成這樣呢?
public static void swap(Entry e1,Entry e2){
Entry temp = e1;//Integer temp = e1.value;
e1 = e2; //e1.value = e2.value;
e2 = temp;//e2.value = temp;
}
再次運行發現結果變成了這樣:
before:a=1,b=2
after :a=1,b=2
before:e1=Entry[value=1000]e2=Entry[value=2000]
after : e1=Entry [value=1000 ]e2=Entry[value=2000]
什么?怎么會這樣?
為了解釋這個問題,我們不妨看一下Java運行時內存結構:
Java Run-Time Data Areas
Java堆 (Java Heap)
作用:存放幾乎所有的對象實例和數組
組成
新生代(Young Generation)
Eden區:存放新創建的對象或短期的對象
Survivor區:存放GC后的幸存的或中期的對象
老年代(Old Generation):存放GC多次后始終存在或者長期的對象及Survivor區放不下的大對象
永久代(Permanent Generation):永久代在JDK8中被完全地移除
是否線程共享:是
Java虛擬機棧(JVM Stacks)
作用:存放棧幀
組成:棧幀
是否線程共享:線程私有的,生命周期和線程的相同
棧幀(Stack Frame)
- 作用:方法在執行的時候,都會有一個棧幀創建出來,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息
- 組成:
- 局部變量表(Local Variables):存放編譯時可知的各種基本數據類型、對象引用
- 操作數棧(Operand Stacks):供方法調用時進行各種運算
- 動態鏈(Dynamic Linking):
- 方法出口
方法區(Method Area)
作用:存放被虛擬機加載的類的結構信息(如:字段和方法數據、方法的字節碼、運行時常量池等)、常量,靜態變量及類、實例、接口初始化時用到的特殊方法。
組成:方法區是堆的邏輯組成部分(有人稱之為永久代 Permanent Generation)
是否線程共享:是
本地方法棧(Native Method Stacks)
作用:存放本地方法調用時的棧幀
組成:棧幀
是否線程共享:線程私有的,生命周期和線程的相同
虛擬機執行Native方法時使用,不同的虛擬機有不同的實現方法,HotSpot虛擬機的本地方法棧和虛擬機棧合二為一。
PC寄存器/程序計數器(pc Register)
作用:保存JVM正在執行方法的字節碼指令的地址,如果該方法為native本地方法則為undefined
組成:一塊至少能夠保存一個本地指針或者returnAddress的值的內存空間
是否線程共享:每個線程都有一個獨立的程序計數器,各個線程之間計數器互不影響,獨立存儲
OK,我們再來分析一下上面的問題:
內存結果分析
其實呢,Java采用的是傳值(call by value),形參只是實際參數的一個拷貝,形參不能修改實參的內容。
當值為基本數據類型時,swap(int,int)方法中的局部變量a,b接收傳入的值并保存在與該方法對應的棧幀的局部變量表中。而main方法中的a,b保存在main方法對應的棧幀的局部變量表中,修改swap方法中的a,b對main方法中的a,b沒有任何影響,所以交換失敗。
當值為引用類型時,傳入方法的也是它的一個拷貝,當然這個拷貝有點特殊,它是Java Heap中的對象(Entry_e1、Entry_e2)的一個引用。該引用也保存在對應的棧幀的局部變量表中,修改swap方法中的e1,e2的引用指向對main方法中的e1,e2沒有任何影響,所以交換失敗。但局部變量e1,e2可以通過引用改變Heap中的對象的狀態,如第一段代碼中在swap中的局部變量可以通過引用來修改Heap中的對象的value屬性,從而達到交換屬性中的目的。
此外,需要注意的是Java中的某些類如:String、基本類型的包裝類、BigInteger、BigDecimal是不可變的,即無法修改其內容。
最后總結一句:Java是方法調用是值傳遞!
總結
以上是生活随笔為你收集整理的Java的call by value_call by value or reference ?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腻子粉那个牌子的好?
- 下一篇: java mytable_Mybatis