读《重构》有感_Java的方法到底是值调用还是引用调用

今天读《重构》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的函数应该将查询和修改

 

猜你喜欢

转载自www.cnblogs.com/muyun/p/9853538.html
今日推荐