今天读《重构》P279,
Separate Query from Modifier,将查询函数和修改函数分离。
突然想到 Java 的传对象作为参数的方法到底是 传引用调用,还是 传值调用?修改参数,会不会影响实参呢?
下面两个代码是不是等效的呢?
//用新值覆盖旧值,并返回 public Student updateStudentInfo(Student newStudent , Student stu){ stu.sex = newStudent.sex; return stu; } Student Tom = new Student(boy); Tom = updateStudentInfo(new Student(girl), Tom); //此时可爱的Tom 就从 boy 变成了 girl 了...
我就在想,如果写成下面这样是不是也对呢?
//用新值覆盖旧值 public void updateStudentInfo(Student newStudent , Student stu){ stu.sex = newStudent.sex; } Student Girl = new Student(girl); Student Tom = new Student(boy); updateStudentInfo(Girl, Tom); //此时,Tom就应该变成 女的了...
然后,我在网络上收集了几个例子,很有意思。
例子1 (原文):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
Employee {
public
String name=
null
;
public
Employee(String n){
this
.name=n;
}
//将两个Employee对象交换
public
static
void
swap(Employee e1,Employee e2){
Employee temp=e1;
e1=e2;
e2=temp;
System.out.println(e1.name+
" "
+e2.name);
//打印结果:李四 张三
}
//主函数
public
static
void
main(String[] args) {
Employee worker=
new
Employee(
"张三"
);
Employee manager=
new
Employee(
"李四"
);
swap(worker,manager);
System.out.println(worker.name+
" "
+manager.name);
//打印结果仍然是: 张三 李四
}
}
|
上面的结果让人很失望,虽然形参对象e1,e2的内容交换了,但实参对象worker,manager并没有互换内容。这里面最重要的原因就在于形参e1,e2是实参worker,manager的地址拷贝。
大家都知道,在Java中对象变量名实际上代表的是对象在堆中的地址(专业术语叫做对象引用 )。在Java方法调用的时候,参数传递的是对象的引用。重要的是,形参和实参所占的内存地址并不一样,形参中的内容只是实参中存储的对象引用的一份拷贝。
如果大家对JVM内存管理中Java栈 的局部变量区 有所了解的话(可以参见《 Java 虚拟机体系结构 》),就很好理解上面这句话。在JVM运行上面的程序时,运行main方法和swap方法,会在Java栈中先后push两个叫做栈帧 的内存空间。main栈帧中有一块叫局部变量区的内存用来存储实参对象worker和manager的引用。而swap栈帧中的局部变量区则存储了形参对象e1和e2的引用。虽然e1和e2的引用值分别与worker和manager相同,但是它们占用了不同的内存空间。当e1和e2的引用发生交换时,下面的图很清晰的看出完全不会影响worker和manager的引用值。
以上是第一个例子。做了个很准确也很有原理性的图,也写出了一个很有意思的现象:交换函数的参数值,似乎并不影响实参。然而,真理到此为止,原文却进一步引申出“Java是传值调用”的鬼话。
为什么这么说呢,我们再看例子2(原文):
public class test { public static void main(String[] args) { Circle c = new Circle(); c.r = 1; bigger(c); System.out.println(c.r); } public static void bigger(Circle c2){ c2.r = 3; } class Circle{ int r; }
输出:3
为什么会是3,看上面的原理图,一目了然。
原文中的代码块1,没有任何问题。
但是,如果在 swap 函数中,修改了 参数e1 和e2 的属性 name , 最后 manger 和 worker 的 name 属性值也会真的被修改!
这不就正说明了 public static void swap(Employee e1,Employee e2) 这个函数是传引用调用的么,因为这个函数可以修改参数的值。
如果像原文中说的那样,是“传值调用”,那么,我在 swap 函数中怎么 改变 e1.name 都不会影响 worker 和 manager 的值的!
利用原文的原理图,也能看出来,Java的【引用调用】比较“奇葩”。
Java的【引用调用】是不安全的,会修改传入的实参的值。根据《重构》P279规约,Java的函数应该将查询和修改