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过程中看看监听器的操作的,老师一笔带过,属实有点蒙圈了,开始我推测是形参实参的问题,后来又感觉是不是监听器调用的问题,最后又回到了形参实参上,实在是因为这个包装类比较特殊,让我迷惑不解了,就连标题我都改了两三回(因为推测错误了)。不过这次也算歪打正着,抓到了包装类这个牛鬼蛇神,希望以后能引以为戒。

posted @ 2022-03-07 18:43  可乐加品客  阅读(1125)  评论(0编辑  收藏  举报