java对形参操作能否改变实参
这个问题其实以前就断断续续的纠结过,这次机缘巧合之下稍微深入的理解了这个问题。
这里的问题是:在主方法里创建了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过程中看看监听器的操作的,老师一笔带过,属实有点蒙圈了,开始我推测是形参实参的问题,后来又感觉是不是监听器调用的问题,最后又回到了形参实参上,实在是因为这个包装类比较特殊,让我迷惑不解了,就连标题我都改了两三回(因为推测错误了)。不过这次也算歪打正着,抓到了包装类这个牛鬼蛇神,希望以后能引以为戒。