1. 程式人生 > >java基本資料型別傳遞與引用傳遞區別詳解

java基本資料型別傳遞與引用傳遞區別詳解

java的值傳遞和引用傳遞在面試中一般都會都被涉及到,今天我們就來聊聊這個問題,首先我們必須認識到這個問題一般是相對函式而言的,也就是java中的方法引數,那麼我們先來回顧一下在程式設計語言中有關引數傳遞給方法(或函式)的兩個專業術語:

  • 按值呼叫(call by value)

  • 按引用呼叫(call by reference)

所謂的按值呼叫表示方法接收的是呼叫著提供的值,而按引用呼叫則表示方法接收的是呼叫者提供的變數地址(如果是C語言的話來說就是指標啦,當然java並沒有指標的概念)。這裡我們需要注意的是一個方法可以修改傳遞引用所對應的變數值,而不能修改傳遞值呼叫所對應的變數值,這句話相當重要,這是按值呼叫與引用呼叫的根本區別

,當然如果還不理解,沒關係,下面就要圖文並茂的徹底分析啦。

前面我們說過java中並不存在引用呼叫,這點是沒錯的,因為java程式設計語言確實是採用了按值呼叫,即call by value。也就是說方法得到的是所有引數值的一個拷貝,方法並不能修改傳遞給它的任何引數變數的內容。下面我們來看一個例子:

package com.zejian.test;
/**
 * java中的按值呼叫
 * @author zejian
 */
public class CallByValue {
	
	private static int x=10;
	
	public static void updateValue(int value){
		value = 3 * value;
	}
	
	public static void main(String[] args) {
		System.out.println("呼叫前x的值:"+x);
		updateValue(x);
		System.out.println("呼叫後x的值:"+x);
	}
	
}

執行程式,結果如下:

呼叫前x的值:10

呼叫後x的值:10

可以看到x的值並沒有變化,接下來我們一起來看一下具體的執行過程:


分析:

1)value被初始化為x值的一個拷貝(也就是10)

2)value被乘以3後等於30,但注意此時x的值仍為10!

3)這個方法結束後,引數變數value不再使用,被回收。

結論:當傳遞方法引數型別為基本資料型別(數字以及布林值)時,一個方法是不可能修改一個基本資料型別的引數。

當然java中除了基本資料型別還有引用資料型別,也就是物件引用,那麼對於這種資料型別又是怎麼樣的情況呢?我們還是一樣先來看一個例子:

宣告一個User物件型別:

package com.zejian.test;
public class User {
	private String name;
	private int age;
	public User(String name, int age) {
		this.name=name;
		this.age=age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
執行類如下:
package com.zejian.test;
/**
 * java中的按值呼叫
 * @author zejian
 */
public class CallByValue {
	private static User user=null;
	public static void updateUser(User student){
		student.setName("Lishen");
		student.setAge(18);
	}
	
	
	public static void main(String[] args) {
		user = new User("zhangsan",26);
		System.out.println("呼叫前user的值:"+user.toString());
		updateUser(user);
		System.out.println("呼叫後user的值:"+user.toString());
	}
}

執行結果如下:

呼叫前user的值:User [name=zhangsan, age=26]

呼叫後user的值:User [name=Lishen, age=18]

很顯然,User的值被改變了,也就是說方法引數型別如果是引用型別的話,引用型別對應的值將會被修改,下面我們來分析一下這個過程:


過程分析:

1)student變數被初始化為user值的拷貝,這裡是一個物件的引用。

2)呼叫student變數的set方法作用在這個引用物件上,user和student同時引用的User物件內部值被修改。

3)方法結束後,student變數不再使用,被釋放,而user還是沒有變,依然指向User物件。

結論:當傳遞方法引數型別為引用資料型別時,一個方法將修改一個引用資料型別的引數所指向物件的值。

雖然到這裡兩個資料型別的傳遞都分析完了,也明白的基本資料型別的傳遞和引用資料型別的傳遞區別,前者將不會修改原資料的值,而後者將會修改引用所指向物件的值。可通過上面的例項我們可能就會覺得java同時擁有按值呼叫和按引用呼叫啊,可惜的是這樣的理解是有誤導性的,雖然上面引用傳遞表面上體現了按引用呼叫現象,但是java中確實只有按值呼叫而沒有按引用呼叫。到這裡估計不少人都蒙逼了,下面我們通過一個反例來說明(回憶一下開頭我們所說明的按值呼叫與按引用呼叫的根本區別)。

package com.zejian.test;
/**
 * java中的按值呼叫
 * @author zejian
 */
public class CallByValue {
	private static User user=null;
	private static User stu=null;
	
	/**
	 * 交換兩個物件
	 * @param x
	 * @param y
	 */
	public static void swap(User x,User y){
		User temp =x;
		x=y;
		y=temp;
	}
	
	
	public static void main(String[] args) {
		user = new User("user",26);
		stu = new User("stu",18);
		System.out.println("呼叫前user的值:"+user.toString());
		System.out.println("呼叫前stu的值:"+stu.toString());
		swap(user,stu);
		System.out.println("呼叫後user的值:"+user.toString());
		System.out.println("呼叫後stu的值:"+stu.toString());
	}
}

我們通過一個swap函式來交換兩個變數user和stu的值,在前面我們說過,如果是按引用呼叫那麼一個方法可以修改傳遞引用所對應的變數值,也就是說如果java是按引用呼叫的話,那麼swap方法將能夠實現資料的交換,而實際執行結果是:

呼叫前user的值:User [name=user, age=26]

呼叫前stu的值:User [name=stu, age=18]

呼叫後user的值:User [name=user, age=26]

呼叫後stu的值:User [name=stu, age=18]

我們發現user和stu的值並沒有發生變化,也就是方法並沒有改變儲存在變數user和stu中的物件引用。swap方法的引數x和y被初始化為兩個物件引用的拷貝,這個方法交換的是這兩個拷貝的值而已,最終,所做的事都是白費力氣罷了。在方法結束後x,y將被丟棄,而原來的變數user和stu仍然引用這個方法呼叫之前所引用的物件。


這個過程也充分說明了java程式設計語言對物件採用的不是引用呼叫,實際上是物件引用進行的是值傳遞,當然在這裡我們可以簡單理解為這就是按值呼叫和引用呼叫的區別,而且必須明白即使java函式在傳遞引用資料型別時,也只是拷貝了引用的值罷了,之所以能修改引用資料是因為它們同時指向了一個物件,但這仍然是按值呼叫而不是引用呼叫。

總結:

  • 一個方法不能修改一個基本資料型別的引數(數值型和布林型)。

  • 一個方法可以修改一個引用所指向的物件狀態,但這仍然是按值呼叫而非引用呼叫。

  • 上面兩種傳遞都進行了值拷貝的過程。

    參考資料:java核心卷1