前言
前段時間在群里看到類似這樣一個問題,下面的代碼會輸出什么呢?
public void test() {
String str = "hello";
change(str);
System.out.println(str);
}
private void change(String str) {
str = "world";
}
當時看到這題,瞬間勾起了我的回憶,遙想當年,也曾經碰到過類似的問題,當時研究了好久才搞明白,這里再記錄一下這個問題的思路,
先來說一下答案:輸出:hello;
解決這類問題首先要搞明白Java到底是參考傳遞還是值傳遞,
Java到底是參考傳遞還是值傳遞
首先來解釋一下什么是參考傳遞,什么是值傳遞,
- 參考傳遞(pass by reference)是指在呼叫方法時將實際引數的地址直接傳遞到方法中,那么在方法中對引數所進行的修改,將影響到實際引數,
- 值傳遞(pass by value)是指在呼叫方法時將實際引數拷貝一份傳遞到方法中,這樣在方法中如果對引數進行修改,將不會影響到實際引數,
那在Java中到底是參考傳遞還是值傳遞呢?其實這個問題也一直是爭論不斷,而且官方也沒給個確切答案,但是就我個人理解,Java是值轉遞,
我們先來看一個簡單的例子:
public void test() {
int a = 1;
change(a);
System.out.println("a的值:" + a);
}
private void change(int a) {
a = a + 1;
}
// 輸出
a的值:1
在test()方法中定義了一個基本型別的變數a,然后呼叫change()方法試圖改變這個變數,最后輸出的還是原來的值,
首先我們要清楚,一個方法中的區域變數是存在堆疊中的,如果是基本型別的變數則直接存的是這個變數的值,如果是參考型別的變數則存的是值的地址,指向堆中具體的值,
上面的例子中,呼叫change()方法傳遞的a,其實是a變數的拷貝,不是真正的a,在change()方法中改變的是拷貝,對真正的a是沒有影響的,
這么一看,Java確實是值傳遞,但是我們再看下面這個例子,你就會糾結了
public void test() {
User user = new User();
user.setAge(18);
change(user);
System.out.println("年齡:" + user.getAge());
}
private void change(User user) {
user.setAge(19);
}
// 輸出
年齡:19
看,物件里的屬性被改變了,不是值傳遞嗎,應該不會改變啊,這時候就有人總結了,當傳的值是基本型別時是值傳遞、當傳的是參考型別時是參考傳遞,真的是這樣嗎?
分析這個問題,我們需要知道變數在jvm中是怎么存盤的,
首先看基本型別,這個很簡單,變數在堆疊中直接存的是值,傳到change()方法的是這個變數的拷貝,因此對拷貝的變數修改不會影響原變數的值,
接著看參考型別,變數在堆疊中存盤的是參考地址,這個地址指向堆中具體的值,如下圖:

當呼叫change()方法傳入變數時,也是拷貝變數,但是這里的拷貝只是堆疊中的參考地址,并不會拷貝堆中的資料,因此會變成下圖這樣:

雖然變數是拷貝,但是指向的地址是同一個,因此對變數中的資料修改時,還是會影響到原來真實的變數,但是,如果我們修改的是變數在堆疊中的地址,則不會影響原變數,例如下面這段代碼:
public void test() {
User user = new User();
user.setAge(18);
change(user);
System.out.println("年齡:" + user.getAge());
}
private void change(User user) {
user = new User();
user.setAge(19);
}
// 輸出
年齡:18
這種是修改變數在堆疊中的地址,則不會影響原變數,
說到這里,大家差不多懂了,但是回頭看最開始的那個問題,傳入String型別的變數,String是參考型別,按道理,原變數是會被改變的呀,結果怎么是不變呢?
String變數比較特殊,我們看String的原始碼可以知道,String的值是通過內部的char[]陣列來維護的,但是這個資料定義的是final型別的,因此,String的值是不可變的,我們平時修改String的值,其實是重新new了一個String物件,例如下面這段代碼:
String a = "hello";
a = "world";
這段代碼里,其實a變數并沒有被修改成world,只是重新new了一個String物件,這個物件的值是world,并把這個物件的參考地址賦給了a,原來的hello還是在堆中,只是這個值沒有被參考,過段時間會被gc垃圾回收,
String變數傳值在記憶體中的變化如下圖:

String拷貝的是變數地址,但是它改變不了原String的值,因為String是不可變的,所以在change()方法中是重新new了一個String物件,改變的是新物件的值,原變數是沒有影響的,
結論
Java是值傳遞,當傳的是基本型別時,傳的是值的拷貝,對拷貝變數的修改不影響原變數;當傳的是參考型別時,傳的是參考地址的拷貝,但是拷貝的地址和真實地址指向的都是同一個真實資料,因此可以修改原變數中的值;當傳的是String型別時,雖然拷貝的也是參考地址,指向的是同一個資料,但是String的值不能被修改,因此無法修改原變數中的值,
關注公眾號:java寶典
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/254303.html
標籤:Java
上一篇:JVM-虛擬機堆疊
下一篇:封裝

