为什么说Java中只有值传递?
跳到导航
跳到搜索
关于
调用一个有参函数的时候,会把实际参数传递给形式参数。 但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。
形参 与 实参
- 形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。【参数的占位符】
- 实际参数:在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。【参数的真实内容】
示例:
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" } }
值传递 与 引用传递
- 值传递(pass by value):在调用函数时将复制的实参值传递到函数中;
- 函数中对参数进行的修改,不会影响实参。
- 引用传递(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
“值传递”的理解
关于“值传递”,有几个常见的错误理解:
- (参数类型)如果传递的是“普通类型”,那就是“值传递”;如果传递的是“对象类型”,那就是“引用传递”。
- (参数内容)“值传递”和“引用传递”的区别是“传递的内容”:如果是个值,就是值传递;如果是个引用,就是引用传递。
辨析
- 示例一:(错误理解)
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 改变了,好像是“如果传递的是对象类型,那就是引用传递”,或“传递的内容是个引用,就是引用传递”。
- 示例二:(辨析理解)
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'}
两个示例的执行过程:
- 在 main 中:
- 创建 User 对象时,会在堆中开辟一块内存(0x11111111),并交由 usr 指向到该内存地址;
- 调用 pass 方法时,会将“实参”(usr)传递给“形参”(user),此时“usr”、“user”都将会指向内存(0x11111111)。
- 在 pass 中:
- 若直接调用“user.setName()”等方法,则将会修改内存(0x11111111)中的内容;
- 若首先调用“user = new User();”方法,则将在堆中重新开辟一块内存(0x22222222),并将“形参”(user)指向该新内存地址;
- 此时再调用“user.setName()”等方法,则将会修改内存(0x22222222)中的内容;
- 所以:传递“对象类型”时,是把“实参”对象所引用的“内存地址”当做值传递给了“形参”。
而输出内容的差别,则是因为:
- 示例一,输出不同:通过“形参”(由于值传递,指向与“实参”相同的内存),修改了内存地址中所存储的数据。
- 修改的是内存中的数据,并没有修改“形参”,更没有影响到“实参”;
- 示例二,输出相同:修改“形参”(由于 new,指向了与“实参”不同的内存),输出内容也分别是两个内存块的数据(独立,但属性相同)。
- 修改的是“形参”,而并没有影响到“实参”;
由此可以看出: 无论是传递“普通类型”还是“对象类型”,所使用的都是“值传递”。 同时可以看出: 值传递和引用传递的区别,是实参到底有没有被复制一份给形参,而不是并不是传递的内容(如上:传递引用,仍然是“值传递”)
按共享传递
无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy),在求值策略中,还有一种叫做按“共享传递”(call by sharing)。
其实 Java 中的参数传递严格意义上说应该是:按共享传递。
按共享传递——是指在调用函数时,传递给函数的是“实参的地址的拷贝”(如果实参在栈中,则直接拷贝该值)。 在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作: 1、如果原值在栈中(实参值即为栈中值),那么直接拷贝该值。 ——因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。 2、如果原值在堆中(实参值为堆中的地址),则需先根据该地址找到堆中对应的位置,再进行操作。 ——因为传递的是地址的拷贝,所以函数内对值的操作对外部变量是可见的。(如上一节的示例)
总之:
- Java 中的传递,是值传递,而这个值,实际上是对象的引用。
- “按共享传递”可以看作“按值传递”的一个特例。