一、前言

  首先先說結論,Java中方法引數傳遞方式是按值傳遞。如果引數是基本型別,傳遞的是基本型別的字面量值的拷貝。如果引數是引用型別,傳遞的是該參量所引用的物件在堆中地址值的拷貝。

  接下來深入瞭解一下為什麼是值傳遞,要想知道Java到底是傳值還是傳引用,首先要知道基本型別和引用型別的區別。

二、深入瞭解引數傳遞

  1.基本型別 和 引用型別的不同之處

基本型別包括8種資料型別:int、short、long、byte、char、float、double、boolean,在方法中定義的非全域性基本資料型別變數的具體內容是儲存在棧中的;除了基本型別以外的都是引用型別:類、介面型別、陣列型別、字串型別都是引用型別,引用型別變數其具體內容都是存放在堆中的,而棧中存放的是其具體內容所在記憶體的地址(字串型別比較特殊,涉及到字串常量池,這裡不做深入研究)。

示例:

int num = 10;      
String str = new String("hello");

注:該圖的堆區是經過簡化的,實際的情況會複雜點,這裡只作示意

如圖所示,num是基本型別,值就直接儲存在變數中。而str是引用型別,變數中儲存的只是實際物件的地址。一般稱這種變數為"引用",引用指向實際物件,實際物件在堆中並儲存著實際內容。

  2.賦值運算子(=)的作用

num = 20;
str = "world";
注:該圖的堆區是經過簡化的,實際的情況會複雜點,這裡只作示意
 
對於基本型別 num ,賦值運算子會直接改變變數的值,原來的值被覆蓋掉。
對於引用型別 str,賦值運算子會改變引用中所儲存的地址,原來的地址被覆蓋掉。但是原來的物件不會被改變。如上圖所示,"world" 字串物件沒有被改變。(沒有被任何引用所指向的物件是垃圾,會被垃圾回收器回收)。
  3.值傳遞和引用傳遞的區別
值傳遞是指在呼叫函式時將實際引數複製一份傳遞到函式中,引用傳遞是指在呼叫函式時將實際引數的地址直接傳遞到函式中,所以值傳遞和引用傳遞的根本區別是是否會複製一份副本。
  4.例項
  例子一(基本資料型別):  
public static void main(String[] args) {
Test test = new Test(); int i = 10;
test.print(10);
System.out.println("main方法輸出i:" + i);
} public void print(int j) {
j = 20;
System.out.println("print方法輸出j:" + j);
}

  結果:

print方法輸出j:20
main方法輸出i:10

這個例子應該還是很好理解的,test.print(10)將10作為引數傳給print方法, 將10拷貝一份給 j,修改 j 不會影響 i 。

  例子二(引用資料型別):

public static void main(String[] args) {
Test test = new Test();
User user = new User();
user.setName("Tom");
user.setAge("18");
test.print(user);
System.out.println("main方法輸出使用者:" + user);
} public void print(User user1) {
user1.setName("Mike");
System.out.println("print方法輸出使用者:" + user1);
}

  結果:

print方法輸出使用者:User{name="Mike",age="18"}
main方法輸出使用者:User{name="Mike",age="18"}

  解釋: test.print(user)將user物件的引用(即user物件的記憶體地址)拷貝一份給形參的user1,也就是說main方法中的user引用和print方法的user1引用都指向堆中的同一個user物件,所以user1修改user物件的資料,user的也會相應的改變。

  user1.setName("Mike")執行前

  user1.setName("Mike")執行後

注意:引用資料型別中的字串型別比較特殊,String被設計成為了不可變型別,為String賦值時不會覆蓋以前的物件而是引用一個新的字串物件(如果新的字串在常量池中直接返回其引用,否則建立一個字串物件,詳情可以去我的另一篇部落格淺析Java常量池 - pluto_blog - 部落格園 (cnblogs.com)),在這裡我們不考慮新字串在常量池的情況。

  下面來看各String型別的例子:
public static void main(String[] args) {
Test test = new Test();
String name = new String("Tom");
test.print(name);
System.out.println("main方法輸出name:" + name);
} public void print(String name1) {
name1 = "Mike";
System.out.println("print方法輸出name1:" + name1);
}

  結果:

print方法輸出name1:Mike
main方法輸出name:Tom

  解釋:由於String被設計成為了不可變型別,為name賦值時不會覆蓋以前的物件而是建立一個新的字串物件並返回引用。

  name1 = "Mike"執行前

  name1 = "Mike"執行後

三、總結

Java中方法引數傳遞方式是按值傳遞。如果引數是基本型別,傳遞的是基本型別的資料拷貝。如果引數是引用型別,因為棧中存的是物件的地址值,所以傳遞的是該參量所引用的物件在堆中地址值的拷貝,除了特殊的String型別,形參物件可以影響實參物件的值。