Java 方法参数的值传递和引用传递
在Java中,实际上都是值传递,并不存在引用传递。人们所说的引用传递,实则为指针间地址的传递。
要理解此,我们必须明白一个概念,形如
Object o;
这样的定义,实际上我们在定义一个指针。当我们把此指针传入方法形参,实则是把此指针所指向的地址传递给了方法形参,而不是对象本体的传入,也就是没有进行真正意义上的引用传递了。关于此问题的更多分析,请参阅笔者之前的一篇译文《我靠!Java就是值传递!》
举例分析
最常见的例子
class Student {
int id;
public Student(int id) {
super();
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
public class Main {
public static void changeId(Student student, int newId) {
student.setId(newId);
student = new Student(-1);
}
public static void main(String[] args) {
Student student = new Student(10);
System.out.println(student);
changeId(student, 0x7fffffff);
System.out.println(student);
}
}
控制台输出:
Student [id=10]
Student [id=2147483647]
传入的student将自身持有的地址赋给形参student。方法的功能为改变学号,那么方法会利用形参改变形参所指向的Student对象的Id。在方法的第二句中,方法改变了形参的指向,将一个新对象的地址赋值给形参student。但注意实参student并没有改变指向,最后的输出依旧是原来的那个对象, 且ID为为未改变形参指向时新赋予的0x7fffffff。此案例再次证明了Java中仅有“值传递”的概念。
一个关于Integer的例子
猜猜下面的输出:
public class Main {
public static void inc(Integer integer) {
integer+=1;
System.out.println("inc: " + integer.toString());
}
public static void main(String[] args) {
Integer integer = new Integer(10);
inc(integer);
System.out.println(integer);
}
}
相信一定会有人认为最后这个Integer对象会改为11,实际上并非如此。控制台输出如下:
inc: 11
10
在inc方法中形参已经改为11了,但是实参并没有,什么鬼?首先我们应该理解Integer是一个典型的不可变类。Integer中没有对值的set相关方法,如果为一个Integer赋予新的值,那么它实际上会指向一块新的内存区域,而非将原有指向的区域的值改变。回头看着inc方法,首先实参integer把自己的地址传给形参integer,然后在方法体中对形参integer进行加一操作。正如上所述,integer实际上没有改变原来指向10,而是指向了新的地址,这个地址存有11这个数。这也就是为什么在最后依旧输出10的原因了。
那么如何解决无法改变外部Integer的这个问题呢?在未来遇到这个需求时我们该作何处理?
一个可行的方法就是定义自己的整型包装类,内部封装一个int值,然后为这个值添加setter和gatter。
总结
明白Java中只有值传递的概念,但是注意对不可变类(如Integer)的特别分析。