最近跟Java中的值传递和引用传递杠上了,一度怀疑人生。查了不少资料,加上本身的理解,终于搞清楚了,什么是值传递和引用传递。也搞明白了,为何你们都说Java只有值传递,没有引用传递。原来,我一直以来的认知都是错误的。。。segmentfault
首先,须要了解一些概念性的东西。ide
形参与实参:函数
形参,是指在定义函数时使用的参数,目的是用于接收调用该函数时传入的参数。简单理解,就是全部函数(即方法)的参数都是形参。this
实参,是指调用函数时,传递给函数的参数。spa
public static void main(String[] args) { int num = 3; printVal(num); //这里num是实参 } private static void printVal(int num) { num = 5; //这里num就是形参 }
值传递和引用传递3d
值传递:是指在调用函数时,将实际参数复制一份传递给函数,这样在函数中修改参数时,不会影响到实际参数。其实,就是在说值传递时,只会改变形参,不会改变实参。code
引用传递:是指在调用函数时,将实际参数的地址传递给函数,这样在函数中对参数的修改,将影响到实际参数。对象
这里,须要特别强调的是,千万不要觉得传递的参数是值就是值传递,传递的是引用就是引用传递。也不要觉得传递的参数是基本数据类型就是值传递,传递的是对象就是引用传递。 这是大错特错的。之前的我,一直都是这样认为的,如今想来真是太天真了。判断是值传递仍是引用传递的标准,和传递参数的类型是没有一毛钱关系的。blog
下面三种状况,基本上能够涵盖全部状况的参数类型。内存
当传递的参数是基本数据类型时:
public class TestNum { public static void main(String[] args) { int num = 3; System.out.println("修改前的num值:"+num); changeValue(num); System.out.println("修改后的num值:"+num); } private static void changeValue(int num) { num = 5; System.out.println("形参num值:"+num); } }
打印结果:
修改前的num值:3 形参num值:5 修改后的num值:3
能够发现,传递基本数据类型时,在函数中修改的仅仅是形参,对实参的值的没有影响。
须要明白一点,值传递不是简单的把实参传递给形参,而是,实参创建了一个副本,而后把副本传递给了形参。下面用图来讲明一下参数传递的过程:
图中num是实参,而后建立了一个副本temp,把它传递个形参value,修改value值对实参num没有任何影响。
传递类型是引用类型时:
public class User { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User(int age, String name) { this.age = age; this.name = name; } public User() { } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } } public class TestUser { public static void main(String[] args) { User user = new User(18, "zhangsan"); System.out.println("修改对象前:"+user); changeUser(user); System.out.println("修改对象后:"+user); } private static void changeUser(User user) { user.setAge(20); user.setName("lisi"); } }
打印结果:
修改对象前:User{age=18, name='zhangsan'} 修改对象后:User{age=20, name='lisi'}
能够发现,传过去的user对象,属性值被改变了。因为,user对象存放在堆里边,其引用存放在栈里边,其参数传递图以下:
user是对象的引用,为实参,而后建立一个副本temp,把它传递给形参user1。可是,他们实际操做的都是堆内存中的同一个User对象。所以,对象内容的修改也会体现到实参user上。
传递类型是String类型(Integer等基本类型的包装类等同)
public class TestStr { public static void main(String[] args) { String str = new String("zhangsan"); System.out.println("字符串修改前:"+str); changeStr(str); System.out.println("字符串修改后:"+str); } private static void changeStr(String str) { str = "lisi"; } }
打印结果:
字符串修改前:zhangsan 字符串修改后:zhangsan
咦,看到这是否是感受有点困惑。按照第二种状况,传递参数是引用类型时,不是能够修改对象内容吗,String也是引用类型,为何在这又不变了呢?
再次强调一下,传递参数是引用类型,并不表明就是引用传递,其实它仍是值传递。此时的 lisi 和上边的 zhangsan 根本不是同一个对象。画图理解下:
图中,str是对象 zhangsan 的引用,为实参,而后建立了一个副本temp,把它传递给了形参str1。此时,建立了一个新的对象 lisi ,形参str1指向这个对象,可是原来的实参str仍是指向zhangsan。所以,形参内容的修改并不会影响到实参内容。因此,两次打印结果都是zhangsan。
第三种状况和第二种状况虽然传递的都是引用类型变量,可是处理方式却不同。第三种状况是建立了一个新的对象,而后把形参指向新对象,而第二种状况并无建立新对象,操做的仍是同一个对象。若是把上边changeUser方法稍做改变,你就会理解:
private static void changeUser(User user) { //添加一行代码,建立新的User对象 user = new User(); user.setAge(20); user.setName("lisi"); }
运行以上代码,你就会惊奇的发现,最终打印修改前和修改后的内容是如出一辙的。
这种状况,就等同于第三种状况。由于,这里的形参和实参引用所指向的对象是不一样的对象。所以,修改形参对象内容并不会影响实参内容。
修改对象前:User{age=18, name='zhangsan'} 修改对象后:User{age=18, name='zhangsan'}
总结:
从以上三个例子中,咱们就能理解了,为何Java中只有值传递,并无引用传递。值传递,不论传递的参数类型是值类型仍是引用类型,都会在调用栈上建立一个形参的副本。不一样的是,对于值类型来讲,复制的就是整个原始值的复制。而对于引用类型来讲,因为在调用栈中只存储对象的引用,所以复制的只是这个引用,而不是原始对象。
最后,再次强调一下,传递参数是引用类型,或者说是对象时,并不表明它就是引用传递。引用传递不是用来形容参数的类型的,不要被“引用”这个词自己迷惑了。这就如同咱们生活中说的地瓜不是瓜,而是红薯同样。