這個問題其實以前就斷斷續續的糾結過,這次機緣巧合之下稍微深入的理解了這個問題。
這里的問題是:在主方法里創建了N個一般屬性,將這些屬性傳遞給其他方法,當其他方法改變了傳遞來的形參屬性的值,主方法內的這些實參屬性是否還會變化?
首先直接上結論:
- 可以把java方法傳參大致分為三種情況:基本類型屬性,包裝類型對象屬性,其他引用類型對象屬性。
- 基本類型與包裝類型一樣,對形參傳過來的參數是不會改變實參的。
- 類對象不同,它是可以“改變”的。
以下為我的發現過程:
疑問提出
在學習GUI的過程中寫了這么一段代碼:
public class Calculate {
public static void main(String[] args) {
MyFrame0 myFrame0 = new MyFrame0();
}
}
//設計一個加法器
class MyFrame0 extends Frame{
public MyFrame0(){
//數字1,數字2,結果
TextField t1 = new TextField();
TextField t2 = new TextField();
TextField t3 = new TextField();
Label label = new Label("+");
Button b2 = new Button("=");
setLayout(new FlowLayout());
setVisible(true);
add(t1);
add(label);
add(t2);
add(b2);
add(t3);
pack();
b2.addActionListener(new MyListener(t1,t2,t3));//按鈕加監聽器
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
//監聽器
class MyListener implements ActionListener{
TextField t1,t2,t3 = null;
public MyListener(TextField t1,TextField t2,TextField t3){
this.t1 = t1;
this.t2 = t2;
this.t3 = t3;
}
@Override
public void actionPerformed(ActionEvent e) {
Integer num1 = Integer.parseInt(t1.getText());
Integer num2 = Integer.parseInt(t2.getText());
t3.setText(""+(num1+num2));
t1.setText("");
t2.setText("");
}
}
代碼有點長,過程邏輯大致為:
- 執行
MyFrame
的構造方法,其內部調用了監聽器ActionListener
的構造方法,監聽器類里面的t1,t2,t3各自指向三個文本框。 - 外部事件,如點擊了button等於號,觸發監聽器,調用監聽器里的
actionPerformed
方法。 - 該方法通過獲取傳過來的t1,t2里面的值,計算出t3里面寫的值,並將自己類里面的t1,t2,t3進行了修改。
- 窗口文本框對象t1,t2,t3也發生了改變。
這時候就產生了一個疑問:MyFrame
類構造方法創建的三個文本框對象,通過監聽器的函數傳到監聽器類里面。那么為什么更改了監聽器里面的文本框對象也能影響MyFrame
類里面的這三個文本框對象?
類比問題
由於之前學過包裝類,我便理所當然的認為:包裝類和一般類可以理解為一個性質,所以我可以用包裝類來類比一般類的情況。
於是我寫了一個類似的簡單方法進行對比:
public class test{
public static void main(String[] args) {
Integer num1 = 1;
test2 test2_ = new test2(num1);//構造方法
test2_.change();//change()方法
System.out.println(num1);//輸出
}
}
class test2{
Integer n1 = null;
//構造方法
public test2(Integer n1){
this.n1 = n1;
}
//change()方法
public void change(){
n1 = Integer.valueOf(0);
}
}
輸出結果還是1,也就是說change()方法沒有使得這個變化。(期間有長時間考慮過是不是監聽器本身的特性,但是還是排除了)
分析結論
分析了一下:
-
首先我們可以確定,基本類型的屬性是存儲在棧里面的,舉個例子:
public class test{ public static void main(String[] args) { int num1 = 1; test2 test2_ = new test2(num1);//構造方法 test2_.change();//change()方法 System.out.println(num1);//輸出 } } class test2{ int n1 = 0; //構造方法 public test2(int n1){ this.n1 = n1; } //change()方法 public void change(){ n1 = 0; } }
主函數中邏輯為,先定義
num1
變量,再執行了change()方法,邏輯圖如下:也就是說,change()方法實際改變的是test2類創建對象里面的這個參數值n1,這個參數是基本類型,它的變化是影響不到實參的。
-
我們再來看一般類對象里的,舉個例子:
public class test{ public static void main(String[] args) { Student s1 = new Student(11,"wang"); test2 test = new test2(s1); test.change(); System.out.println(s1.getName()+s1.getAge()); } } class test2{ Student n1 = null; public test2(Student n1){ this.n1 = n1; } public void change(){ n1.setAge(30); n1.setName("nothing"); } }
與上一個的區別在於,對象的創建是在堆中操作的,對象創建之后,對象的各種屬性,方法存儲在堆中:
對於這里的構造函數:
test2 test = new test2(s1);
,其將s1賦給n1,實際上是指,s1和n1共同指向了同一塊堆區域,當n1發生變化,自然s1也會隨之發生變化。 -
對於包裝類,首先包裝類自然是屬於和類一樣的情況,但是它為什么會出現和基本類型一樣的結果,即形參不影響實參呢?
可以看一下
Integer
類型源碼:public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private final int value; public Integer(int value) { this.value = value; }
我們知道,裝箱操作是需要調用valueOf()方法的,源碼中涉及到Integer緩沖區的問題,具體可以參考我的另一篇隨筆:Integer緩沖區相關問題--valueOf()方法,這里不過多贅述。這里的意思就是說,裝箱操作后,最終會讓Integer類里面的value附上值。
注意到final,表明該value屬性一旦賦值不可更改,所以當我們通過外界創建Integer對象時,一旦賦值上了Integer的屬性值就不能改變了,即使你如何操作堆里面的該屬性也不會變。這個結論可以推至其他包裝類。
-
String不屬於包裝類,但是它的變化也可以理解為基本類型與包裝類一樣,都是一個情況。
所以就得出了結論,與開頭結論一致。
ps:本來是學習GUI過程中看看監聽器的操作的,老師一筆帶過,屬實有點蒙圈了,開始我推測是形參實參的問題,后來又感覺是不是監聽器調用的問題,最后又回到了形參實參上,實在是因為這個包裝類比較特殊,讓我迷惑不解了,就連標題我都改了兩三回(因為推測錯誤了)。不過這次也算歪打正着,抓到了包裝類這個牛鬼蛇神,希望以后能引以為戒。