这几天在整理java基础知识方面的内容,对于值传递还不是特别理解,于是查阅了一些资料和网上相关博客,自己进行了归纳总结,最后将其整理成了一篇博客。
值传递是指在调用函数时将实际参数 复制 一份传递给形参,这样在函数中对形参的修改将不会影响到实际参数的值。
引用传递是指在调用函数时将实际参数的地址 直接传递 到形参,那么在函数中对参数所进行的修改,将会影响到实际参数的值。
我们可以使用一段程序来验证 Java 中只有值传递
/**
* 验证java中只有值传递
* Dmego 2018-8-27
*/
class User{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class TestValue {
public static void change(User user2,int a2){
System.out.println("改变之前:"+user2.getName()+",a2="+a2);
user2.setName("李四"); //改变 user2 的 name 值
a2 = 10; //改变 a2 的值
System.out.println("改变之后:"+user2.getName()+",a2="+a2);
user2 = new User(); //将 user2 重新指向一个新对象
user2.setName("王五");
System.out.println("重新指向一个新对象后:"+user2.getName());
}
public static void main(String[] args){
User user1 = new User();
user1.setName("张三"); //初始化 user1 的 name 为张三
int a1 = 5; //初始化 a1 的值为 5
change(user1,a1); //调用方法验证传值方式
System.out.println("调用方法后:"+user1.getName()+",a1="+a1);
}
}
运行这段程序,输出结果为:
改变之前:张三,a2=5 改变之后:李四,a2=10 重新指向一个新对象后:王五 调用方法后:李四,a1=5
下面我们以上图为辅助,来分析这段程序,首先我们定义了一个 User 类,然后在测试类中实例化了一个 User 对象,名为 user1 ,并且为其赋值 name = '张三' ,此时在内存中如 图1 所示,实例化一个对象相当于在堆中开辟了一块内存,内存地址为 017 ,此时这个对象的引用为 user1 ,内存地址为 001 ,它保存了该对象在内存中的地址,也就是指向了该对象。接下了,我们调用方法 changeVaulue() ,来尝试改变 user1 的 name 值以此验证 java 中的传值方式。
我们将 user1 作为实参传给 changeValue() 方法,形参 user2 来接受这个实参,在这里就体现出了两种传参方式的不同。如果是按值传递,那么就像定义的那样,如 图2 所示, user2 是 user1 的一份副本,也就是说在传递参数时,将 user1 (本身是一个对象的引用),复制了一份,名为 user2 ,它同样也是一个对象的引用,并且 user1 和 user2 此时指向同一个对象。而如果是引用传递,也如同定义的那样,如 图5 所示,在传递参数时,是直接将 user1 传递给了形参,只是换了一个名字叫做 user2 ,但是本质上 user1 和 user2 其实是同一个。它是一个对象的引用。
接着来分析输出的结果,不管是按值传递还是引用传递,第1行输出的结果一定都是 张三 ,因为都是指向同一个对象。对于第2行输出,我们还是无法判断是哪种方式,因为都是改变同一个对象,值也会改变;关键在于第3行输出和第4行输出,此时,我们将 user2 重新指向了一个新的对象,并且为这个对象赋值 name = '王五' ,如果是 引用传递 的方式,那么 user1 同样也会改变指向,指向新的这个对象,最后一行调用方法之后输出的结果将会和第3行一样是 王五 ,但是事实输出的是 李四 ,这表明 user1 和 user2 其实并不是同一个。真实的调用过程如 图2 ~ 图4 所示,这样才会使得 user2 指向一个新的对象后, user1 指向的对象并没有改变,还是原来那个对象。
对于基本类型的参数来说, a1 的值最后没有改变,说明在执行方法时, a2 是 a1 的一个副本。对于引用类型的参数来说,例如 User 对象,在调用方法时,实际上是将其引用 user1 作为实际参数,那么传递给形参的将是该引用的一份副本引用 user2 ,虽然说这是两份引用(好比 a1 与 a2 的关系)。但是却指向同一个对象,所有的操作也都是对这同一个对象而言的。
最后举一个例子来形象的说明这一切,假如你有一把你房间的钥匙,并且在上面刻上了你的名字,这个过程好比给一个 int 类型的 a1 初始化值为 5 。你的朋友和你关系非常好,想要你房间的钥匙,此时你并没有直接把你的钥匙给他,而是复制了一把新的钥匙,这个钥匙也能开你的房间的门。而你的朋友在这把新钥匙上刻上了他的名字。这个过程就好比调用 change() 方法,把 a1 复制了一份赋值给 a2 ,此时修改 a2 和 a1 没有任何关系,你朋友在新钥匙上刻他名字也不会影响你手上那把原始的钥匙。关键是这两把钥匙都能开你的房间,就好比 user1 和 user2 都指向同一个对象。此时你朋友用这把新钥匙打开了你的房间,将你房间电视机砸了。这个过程好比改名 李四 。这时你拿着你的钥匙打开你房间必然会看到这样的场景——电视机被砸了。就如同调用方法后 user1 变成了 李四 。在调用方法的过程中,最后 user2 重新指向了一个新的对象,这就好比你的朋友将你复制给他的钥匙再次进行了加工,此时不能开你房间的门,但是能开他自己的房间,他用这把钥匙开自己的房间然后把自己的电视砸了这并不会影响到你房间的电视,也就是说最后 user1 的名字并不会变成 王五 。这就是 java 中的值传递。当然了,如果是引用传递,那么这个例子中从头到尾将会只有一把钥匙,最后的结果也将会不同。
通过以上分析我们可以知道。 Java 中只有 值传递 这一种方式,只不过对于引用类型来说, 传递的参数是对象的引用 罢了。