学习Java中,最大的问题并不是语言本身,而是一些经验和固有的认知,有些基本的知识,已经根深蒂固,但是如果换个编程语言,思维的惯性可能是有害的。
例如,关于按值调用(call by value)和按引用调用(call by reference),这两种方式在C++中已经是司空见惯的老生常谈问题,对于这个现象的描述,swap函数的出场率也是非常高的。
那么,在Java中又是如何?首先,在Java中,对于基本的数字类型,例如int或者Boolean等,传递的都是值的一个副本,下列方法/函数:
void addOne(int x)
{
x+=1;
}
调用前后,作为实参的int变量,其值并不会发生改变,更夸张的是,你甚至可以传递一个int的字面量,而字面量是不能够作为左值的。有一点点C/C++基础的都知道,函数调用的时候,实际上是把参数的副本压入了函数的栈中,当然这个基础的知识也没必要浪费太多的篇幅阐述了。
接下来回到了问题的关键,我们知道,方法在调用的时候,其参数还有可能是对象引用,Java中的情况又是如何呢?我们先写一个类:
class Myc
{
int x;
String name;
public Myc(String name,int x)
{
this.name=name;
this.x=x;
}
public void setX(int x)
{
this.x=x;
}
public int getX()
{
return this.x;
}
@Override
public String toString()
{
return name+":"+this.x;
}
}
然后再定义一个修改其参数的函数,以及在主函数中的测试代码:
public static void main(String[] args)
{
Myc x = new Myc("first object",10);
System.out.println(x);
objAddOne(x);
System.out.println(x);
}
public static void objAddOne(Myc c)
{
c.setX(c.getX()+1);
}
不出意料的,这个程序的输出结果是:
first object:10
first object:11
看样子,通过方法的调用,我们修改了所传入的对象,这么看来,行为和按引用调用很像嘛,但是这个就真的是按引用调用吗?
我们知道,按引用调用的一个显著特征是你可以实现对象引用的交换,以C语言代码为例:
void swap(int* x,int* y)
{
int t=*x;
*x=*y;
*y=t;
}
刚好可以在Java中测试一下类似的方法:
public static void main(String[] args)
{
Myc x = new Myc("first object",10);
Myc y=new Myc("second object",20);
swapObject(x,y);
System.out.println(x);
System.out.println(y);
}
public static void swapObject(Myc a,Myc b)
{
Myc temp=a;
a=b;
b=temp;
}
如果是按引用传递,那么它们应该能够实现交换,实际输出结果为:
public static void main(String[] args)
{
Myc x = new Myc("first object",10);
Myc y=new Myc("second object",20);
swapObject(x,y);
System.out.println(x);
System.out.println(y);
}
public static void swapObject(Myc a,Myc b)
{
Myc temp=a;
a=b;
b=temp;
}
就像什么都没发生一样。那么问题出在哪里呢?实际上,swapObject函数的参数a和b也是两个对象的副本,这个方法交换的,其实是副本,可以在swapObject函数中展开验证一下:
public static void swapObject(Myc a,Myc b)
{
Myc temp=a;
a=b;
b=temp;
System.out.println("In swapObject:");
System.out.println(a);
System.out.println(b);
}
再次运行的结果是:
In swapObject:
second object:20
first object:10
first object:10
second object:20
可以看到,在方法中,确实两个对象产生了交换,但是退出方法以后,所有的努力都化为乌有,这和最开始的那个addOne是一样的效果。
所以根本原因在哪里呢?在Java中,对象引用也是按值传递的。
总结Java中对方法参数的行为:
1. 方法不会修改基本数据类型的参数;
2. 方法可以修改对象参数的状态(例如通过objAddOne实现了对状态x的修改)
3. 方法不能让一个对象参数引用到一个新对象上。
4. 修改对象的状态和修改引用是两回事。
最后的最后,问题的答案是什么?Java的方法并不使用按引用调用。