一、概述
曾經糾結了很久java的引數傳遞方式是什麼樣的,後面粗略的瞭解了一鱗半爪以後有了大概的印象:“傳引數就是值傳遞,傳物件就是引用傳遞”,後面進一步查詢了相關資料和文章以後,發現這麼理解是不正確的。
這裡先放結論:
- java中引數的傳遞可以理解為都是值傳遞
- 基礎資料型別傳遞的是值的拷貝
- 物件型別是共享物件傳遞,傳遞的是地址的拷貝
二、形參和實參
要理解引數的傳遞就必須先理解形參和實參:
-
形參:就是形式引數,用於定義方法的時候使用的引數,是用來接收呼叫者傳遞的引數的。
形參只有在方法被呼叫的時候,虛擬機器才會分配記憶體單元,在方法呼叫結束之後便會釋放所分配的記憶體單元。
因此,形參只在方法內部有效,所以針對引用物件的改動也無法影響到方法外。
-
實參:就是實際引數,用於呼叫時傳遞給方法的引數。
舉個例子:
public static void main( String[] args ) {
String string = "Hello";
//string是實際引數
sout(string);
}
public static void sout(String str){
//str為形式引數
System.out.println(str);
}
三、值傳遞和引用傳遞與共享物件傳遞
1.值傳遞和引用傳遞
理解了實參和形參,以及java對應的資料型別,我們就可以理解值傳遞和引用傳遞了。
-
值傳遞:方法呼叫時,實際引數的值被傳遞給對應的形式引數,函式接收的是原始值的一個copy, 此時記憶體中存在兩個相等的基本型別,即實際引數和形式引數,後面方法中的操作都是對形參這個值的修改,不影響實際引數的值。
-
引用傳遞/址傳遞:方法呼叫時,實際引數的地址被傳遞給方法中相對應的形式引數,函式接收的是原始值的記憶體地址。在方法執行中,形參和實參內容相同,指向同一塊記憶體地址,方法執行中對引用的操作將會影響到實際物件。
對於這兩種方式,網上有一個非常形象的圖:
2.共享物件傳遞
但是java的傳值策略有點類似於兩者的結合,是共享物件傳遞:
- 共享物件傳遞:先獲取到實際引數的地址,然後將其複製,並把該地址的拷貝傳遞給被調函式的形式引數。因為引數的地址都指向同一個物件,所以我們稱也之為"傳共享物件",所以,如果在被調函式中改變了形式引數的值,呼叫者是可以看到這種變化的。
這也是之所以說java也是值傳遞的原因,共享物件傳遞實際上也是對實參進行拷貝然後賦給形參,但是操作針對的物件不是值而是地址!
由於傳遞的是地址的拷貝,所以如果你在方法中將這個地址指向了新的物件,實際上是沒有任何對方法外是沒有任何作用的,舉個例子:
public static void main( String[] args ) {
Person p = new Person();
System.out.println("main中:" + p.hashCode());
change(p);
System.out.println("main中:" + p.hashCode());
}
public static void change(Person person){
person = new Person();
System.out.println("change中:" + person.hashCode());
}
//輸出
main中:692404036
change中:1554874502
main中:692404036
可以看到在main
方法中輸出的hashCode指向的都是同一個物件,而change
中指向了另一個,可以這麼理解:
- p為指向了第一個Person物件的地址
- 把p拷貝了一份得到p‘,這裡的p’就是
change
方法中的形參p change
中p指向了一個新的Person物件,在change
這個函式範圍裡p指向的就是new出來的第二個Person物件的地址- 由於
change
中的p實際上是main
中p的拷貝p‘,所以在change
裡p'指向的改變對main
中的p不會有任何影響
四、總結
你在福建有座倉庫,給自己配了一把鑰匙
1.三種傳遞:
- 值傳遞:你建了一座一模一樣的倉庫給別人
- 引用傳遞:把你家倉庫的鑰匙給了別人
- 共享物件傳遞:把你家倉庫鑰匙復刻了一把給別人
2.共享物件傳遞的特點:
- 拷貝的地址與原地址指向同一個記憶體物件:別人用你復刻的鑰匙一樣能進出你的倉庫
- 拷貝地址引用物件的改變不影響原地址的引用物件:別人在山東也蓋了個倉庫,用你給他的鑰匙配了鎖,他的鑰匙在山東只能開他的倉庫,你的鑰匙在福建只能開你的倉庫