为什么说Java中只有值传递?

来自Wikioe
跳到导航 跳到搜索


关于

调用一个有参函数的时候,会把实际参数传递给形式参数。

但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。

形参 与 实参

  1. 形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。【参数的占位符】
  2. 实际参数:在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。【参数的真实内容】

示例:

public class NormalTest {
    public void print(String str){  // 形参为 str
        System.out.println(str);
    }

    public static void main(String[] args) {
        NormalTest nt = new NormalTest();
        nt.print("Eijux");  // 实参为 "Eijux"
    }
}

值传递 与 引用传递

  1. 值传递(pass by value):在调用函数时将复制的实参值传递到函数中;
    • 函数中对参数进行的修改,不会影响实参
  2. 引用传递(pass by reference):在调用函数时将实参地址直接传递到函数中;
    • 函数中对参数进行的修改,将影响到实参

示例:

public class NormalTest {
    public void print(int j) {
        j = 20;
        System.out.println("print in pass , j is " + j);
    }

    public static void main(String[] args) {
        NormalTest nt = new NormalTest();
        int i = 10;
        nt.print(i);
        System.out.println("print in main , i is " + i);
    }
}
print in pass , j is 20
print in main , i is 10

“值传递”的理解

关于“值传递”,有几个常见的错误理解:

  1. (参数类型)如果传递的是“普通类型”,那就是“值传递”;如果传递的是“对象类型”,那就是“引用传递”。
  2. (参数内容)“值传递”和“引用传递”的区别是“传递的内容”:如果是个值,就是值传递;如果是个引用,就是引用传递。

辨析

  1. 示例一:(错误理解)
    public class NormalTest {
        public void print(User user) {
            user.setName("Xuelinzi");
            System.out.println("print in pass , user is " + user);
        }
    
        public static void main(String[] args) {
            NormalTest nt = new NormalTest();
            User usr = new User();
            usr.setName("Eijux");
            usr.setGender("Male");
    
            nt.print(usr);  // 传递实参 usr
            System.out.println("print in main , user is " + usr);
        }
    
        static class User{
            private String name;
            private String gender;
    
            public void setName(String name){ this.name = name; }
            public void setGender(String gender){ this.gender = gender; }
    
            @Override
            public String toString() {
                return "User{" +
                        "name='" + name + '\'' +
                        ", gender='" + gender + '\'' +
                        '}';
            }
        }
    }
    
    print in pass , user is User{name='Xuelinzi', gender='Male'}
    print in main , user is User{name='Xuelinzi', gender='Male'}
    
    如上所示:看似是在 print 方法中,将实参 usr 改变了,好像是“如果传递的是对象类型,那就是引用传递”,或“传递的内容是个引用,就是引用传递”。
  2. 示例二:(辨析理解)
    public class NormalTest {
        public void pass(User user) {
            user = new User();
            user.setName("Xuelinzi");
            user.setGender("Male");
            System.out.println("print in pass , user is " + user);
        }
    
        public static void main(String[] args) {
            NormalTest nt = new NormalTest();
            User usr = new User();
            usr.setName("Eijux");
            usr.setGender("Male");
    
            nt.pass(usr);  // 传递实参 usr
            System.out.println("print in main , user is " + usr);
        }
    
        static class User{
            private String name;
            private String gender;
    
            public void setName(String name){ this.name = name; }
            public void setGender(String gender){ this.gender = gender; }
    
            @Override
            public String toString() {
                return "User{" +
                        "name='" + name + '\'' +
                        ", gender='" + gender + '\'' +
                        '}';
            }
        }
    }
    
    print in pass , user is User{name='Xuelinzi', gender='Male'}
    print in main , user is User{name='Eijux', gender='Male'}
    


两个示例的执行过程:

  1. 在 main 中:
    1. 创建 User 对象时,会在堆中开辟一块内存(0x11111111),并交由 usr 指向到该内存地址;
    2. 调用 pass 方法时,会将“实参”(usr)传递给“形参”(user),此时“usr”、“user”都将会指向内存(0x11111111)。
  2. 在 pass 中:
    1. 若直接调用“user.setName()”等方法,则将会修改内存(0x11111111)中的内容;
    2. 若首先调用“user = new User();”方法,则将在堆中重新开辟一块内存(0x22222222),并将“形参”(user)指向该新内存地址;
      此时再调用“user.setName()”等方法,则将会修改内存(0x22222222)中的内容;
所以:传递“对象类型”时,是把“实参”对象所引用的“内存地址”当做值传递给了“形参”。


而输出内容的差别,则是因为:

  1. 示例一,输出不同:通过“形参”(由于值传递,指向与“实参”相同的内存),修改了内存地址中所存储的数据
    • 修改的是内存中的数据,并没有修改“形参”,更没有影响到“实参”;
  2. 示例二,输出相同:修改“形参”(由于 new,指向了与“实参”不同的内存),输出内容也分别是两个内存块的数据(独立,但属性相同)。
    • 修改的是“形参”,而并没有影响到“实参”;


由此可以看出:
    无论是传递“普通类型”还是“对象类型”,所使用的都是“值传递”

同时可以看出:
    值传递和引用传递的区别,是实参到底有没有被复制一份给形参,而不是并不是传递的内容(如上:传递引用,仍然是“值传递”)

按共享传递

无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy),在求值策略中,还有一种叫做按“共享传递”(call by sharing)。


其实 Java 中的参数传递严格意义上说应该是:按共享传递

按共享传递——是指在调用函数时,传递给函数的是“实参的地址的拷贝”(如果实参在栈中,则直接拷贝该值)。


在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作:

    1、如果原值在栈中(实参值即为栈中值),那么直接拷贝该值。
        ——因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。

    2、如果原值在堆中(实参值为堆中的地址),则需先根据该地址找到堆中对应的位置,再进行操作。
        ——因为传递的是地址的拷贝,所以函数内对值的操作对外部变量是可见的。(如上一节的示例)

总之:

  • Java 中的传递,是值传递,而这个值,实际上是对象的引用。
  • “按共享传递”可以看作“按值传递”的一个特例。