值传递和引用传递问题一般是相对函数而言的,那说到函数就得提到有关参数传递给方法的两个专业术语:按值调用(call by value)和 按引用调用(call by reference)。
按值调用是说方法接收的是调用者提供的值,而按引用调用则是方法接收的是调用者提供的变量地址(用C语言的话来说就是指针,当然java并没有指针的概念)。
这里需要特别注意的是一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值,这是按值调用与引用调用的根本区别,如果没理解后面会有例子解释说明。
其实java中并不存在引用调用,因为java程序设计语言确实是采用了按值调用,即call by value。也就是说方法得到的是所有参数值的一个拷贝,方法并不能修改传递给它的任何参数变量的内容。代码案例:
1 public class CallByValue { 2 3 private static int Y=5; 4 5 public static void updateValue(int value){ 6 value = 2 * value; 7 } 8 9 public static void main(String[] args) { 10 System.out.println("调用前Y的值:"+Y); 11 updateValue(Y); 12 System.out.println("调用后Y的值:"+Y); 13 } 14 15 }
调用前Y的值:5
调用后Y的值:5
然而发现Y的值并没有改变
分析:
- value被初始化为Y值的一个拷贝(也就是5)
- value被乘以2后等于10,但注意此时Y的值仍为5
- 方法调用结束后,参数变量value不再使用,被回收。
总结:
当传递方法参数类型为基本数据类型(数字以及布尔值)时,一个方法是不可能修改一个基本数据类型的参数。
然而在java中除了基本数据类型外还有引用数据类型,接下来看它的传递情况。
//声明一个User类 public class User { private String name; private int age; public User(String name, int age) { this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
测试代码:
public class CallByValue { private static User user=null; public static void updateUser(User student){ student.setName("YJ"); student.setAge(20); } public static void main(String[] args) { user = new User("Luzun",18); System.out.println("调用前user的值:"+user.toString()); updateUser(user); System.out.println("调用后user的值:"+user.toString()); } }
调用前user的值:User [name=Luzun, age=18]
调用后user的值:User [name=YJ, age=20]
很显然,User的值被改变了,也就是说方法参数类型如果是引用类型的话,引用类型对应的值将会被修改。
分析:
- student变量被初始化为user值的拷贝,这里是一个对象的引用。
- 调用student变量的set方法作用在这个引用对象上,user和student同时引用的User对象内部值被修改。
- 方法结束后,student变量不再使用,被释放,而user还是没有变,依然指向User对象。
总结:
当传递方法参数类型为引用数据类型时,一个方法将修改一个引用数据类型的参数所指向对象的值。
通过上面的测试你可能就会觉得java同时拥有按值调用和按引用调用,然而可惜的是这是有误导性的,虽然上面引用传递表面上体现了按引用调用现象,但是java中确实只有按值调用而没有按引用调用。下面再进行一个测试:
public class CallByValue { private static User user=null; private static User stu=null; public static void swap(User x,User y){ User temp =x; x=y; y=temp; }
public static void main(String[] args) { user = new User("teacher",28); stu = new User("student",16); System.out.println("调用前user的值:"+teacher.toString()); System.out.println("调用前stu的值:"+student.toString()); swap(teacher,student); System.out.println("调用后user的值:"+teacher.toString()); System.out.println("调用后stu的值:"+student.toString()); } }
可以通过一个swap函数来交换两个变量teacher和student的值,如果是按引用调用那么一个方法可以修改传递引用所对应的变量值,也就是说如果java是按引用调用的话,那么swap方法将能够实现数据的交换,而实际运行结果:
调用前teacher的值:User [name=teacher, age=28] 调用前student的值:User [name=student, age=16] 调用后teacher的值:User [name=teacher, age=28] 调用后student的值:User [name=student, age=16]
发现teacher和student的值并没有发生变化,也就是方法并没有改变存储在变量teacher和student中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝的值而已,最终在方法结束后x,y被丢弃,而原来的变量teacher和student仍然引用这个方法调用之前所引用的对象。这个过程也充分说明了java程序设计语言对对象采用的不是引用调用,实际上是对象引用进行的是值传递。java函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。