java對形參操作能否改變實參


這個問題其實以前就斷斷續續的糾結過,這次機緣巧合之下稍微深入的理解了這個問題。

這里的問題是:在主方法里創建了N個一般屬性,將這些屬性傳遞給其他方法,當其他方法改變了傳遞來的形參屬性的值主方法內的這些實參屬性是否還會變化?

首先直接上結論:

  1. 可以把java方法傳參大致分為三種情況:基本類型屬性,包裝類型對象屬性,其他引用類型對象屬性
  2. 基本類型與包裝類型一樣,對形參傳過來的參數是不會改變實參的。
  3. 類對象不同,它是可以“改變”的。

以下為我的發現過程:

疑問提出

在學習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("");
    }
}

代碼有點長,過程邏輯大致為:

  1. 執行MyFrame的構造方法,其內部調用了監聽器ActionListener的構造方法,監聽器類里面的t1,t2,t3各自指向三個文本框。
  2. 外部事件,如點擊了button等於號,觸發監聽器,調用監聽器里的actionPerformed方法。
  3. 該方法通過獲取傳過來的t1,t2里面的值,計算出t3里面寫的值,並將自己類里面的t1,t2,t3進行了修改。
  4. 窗口文本框對象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()方法沒有使得這個變化。(期間有長時間考慮過是不是監聽器本身的特性,但是還是排除了)

分析結論

分析了一下:

  1. 首先我們可以確定,基本類型的屬性是存儲在棧里面的,舉個例子:

    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()方法,邏輯圖如下:

    image-20220307175208240

    也就是說,change()方法實際改變的是test2類創建對象里面的這個參數值n1,這個參數是基本類型,它的變化是影響不到實參的。

  2. 我們再來看一般類對象里的,舉個例子:

    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");
        }
    }
    

    與上一個的區別在於,對象的創建是在堆中操作的,對象創建之后,對象的各種屬性,方法存儲在堆中:

    image-20220307180146107

    對於這里的構造函數:test2 test = new test2(s1);,其將s1賦給n1,實際上是指,s1和n1共同指向了同一塊堆區域,當n1發生變化,自然s1也會隨之發生變化。

  3. 對於包裝類,首先包裝類自然是屬於和類一樣的情況,但是它為什么會出現和基本類型一樣的結果,即形參不影響實參呢?

    可以看一下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的屬性值就不能改變了,即使你如何操作堆里面的該屬性也不會變。這個結論可以推至其他包裝類。

  4. String不屬於包裝類,但是它的變化也可以理解為基本類型與包裝類一樣,都是一個情況。

所以就得出了結論,與開頭結論一致。


ps:本來是學習GUI過程中看看監聽器的操作的,老師一筆帶過,屬實有點蒙圈了,開始我推測是形參實參的問題,后來又感覺是不是監聽器調用的問題,最后又回到了形參實參上,實在是因為這個包裝類比較特殊,讓我迷惑不解了,就連標題我都改了兩三回(因為推測錯誤了)。不過這次也算歪打正着,抓到了包裝類這個牛鬼蛇神,希望以后能引以為戒。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM