转载自知乎-烟雨星空

    值传递和引用传递
    值传递:是指在调用函数时,将实际参数复制一份传递给函数,这样在函数中修改参数时,不会影响到实际参数。其实,就是在说值传递时,只会改变形参,不会改变实参。
    引用传递:是指在调用函数时,将实际参数的地址传递给函数,这样在函数中对参数的修改,将影响到实际参数。
    这里,需要特别强调的是,千万不要以为传递的参数是值就是值传递,传递的是引用就是引用传递。也不要以为传递的参数是基本数据类型就是值传递,传递的是对象就是引用传递。 这是大错特错的。以前的我,一直都是这样认为的,现在想来真是太天真了。判断是值传递还是引用传递的标准,和传递参数的类型是没有一毛钱关系的。
    下面三种情况,基本上可以涵盖所有情况的参数类型。

    当传递的参数是基本数据类型时:

    1. public class TestNum {
    2. public static void main(String[] args) {
    3. int num = 3;
    4. System.out.println("修改前的num值:"+num);
    5. changeValue(num);
    6. System.out.println("修改后的num值:"+num);
    7. }
    8. private static void changeValue(int num) {
    9. num = 5;
    10. System.out.println("形参num值:"+num);
    11. }
    12. }

    打印结果:

    1. 修改前的num值:3
    2. 形参num值:5
    3. 修改后的num值:3

    可以发现,传递基本数据类型时,在函数中修改的仅仅是形参,对实参的值的没有影响。
    需要明白一点,值传递不是简单的把实参传递给形参,而是,实参建立了一个副本,然后把副本传递给了形参。下面用图来说明一下参数传递的过程:
    image.png
    图中num是实参,然后创建了一个副本temp,把它传递个形参value,修改value值对实参num没有任何影响。
    传递类型是引用类型时:

    1. public class User {
    2. private int age;
    3. private String name;
    4. public int getAge() {
    5. return age;
    6. }
    7. public void setAge(int age) {
    8. this.age = age;
    9. }
    10. public String getName() {
    11. return name;
    12. }
    13. public void setName(String name) {
    14. this.name = name;
    15. }
    16. public User(int age, String name) {
    17. this.age = age;
    18. this.name = name;
    19. }
    20. public User() {
    21. }
    22. @Override
    23. public String toString() {
    24. return "User{" +
    25. "age=" + age +
    26. ", name='" + name + '\'' +
    27. '}';
    28. }
    29. }
    30. public class TestUser {
    31. public static void main(String[] args) {
    32. User user = new User(18, "zhangsan");
    33. System.out.println("修改对象前:"+user);
    34. changeUser(user);
    35. System.out.println("修改对象后:"+user);
    36. }
    37. private static void changeUser(User user) {
    38. user.setAge(20);
    39. user.setName("lisi");
    40. }
    41. }

    打印结果:

    1. 修改对象前:User{age=18, name='zhangsan'}
    2. 修改对象后:User{age=20, name='lisi'}

    可以发现,传过去的user对象,属性值被改变了。由于,user对象存放在堆里边,其引用存放在栈里边,其参数传递图如下:
    image.png
    user是对象的引用,为实参,然后创建一个副本temp,把它传递给形参user1。但是,他们实际操作的都是堆内存中的同一个User对象。因此,对象内容的修改也会体现到实参user上。

    传递类型是String类型(Integer等基本类型的包装类等同)

    1. public class TestStr {
    2. public static void main(String[] args) {
    3. String str = new String("zhangsan");
    4. System.out.println("字符串修改前:"+str);
    5. changeStr(str);
    6. System.out.println("字符串修改后:"+str);
    7. }
    8. private static void changeStr(String str) {
    9. str = "lisi";
    10. }
    11. }

    打印结果:

    1. 字符串修改前:zhangsan
    2. 字符串修改后:zhangsan

    咦,看到这是不是感觉有点困惑。按照第二种情况,传递参数是引用类型时,不是可以修改对象内容吗,String也是引用类型,为什么在这又不变了呢?
    再次强调一下,传递参数是引用类型,并不代表就是引用传递,其实它还是值传递。此时的 lisi 和上边的 zhangsan 根本不是同一个对象。画图理解下:
    image.png
    图中,str是对象 zhangsan 的引用,为实参,然后创建了一个副本temp,把它传递给了形参str1。此时,创建了一个新的对象 lisi ,形参str1指向这个对象,但是原来的实参str还是指向zhangsan。因此,形参内容的修改并不会影响到实参内容。所以,两次打印结果都是zhangsan。
    第三种情况和第二种情况虽然传递的都是引用类型变量,但是处理方式却不一样。第三种情况是创建了一个新的对象,然后把形参指向新对象,而第二种情况并没有创建新对象,操作的还是同一个对象。如果把上边changeUser方法稍作改变,你就会理解:

    1. private static void changeUser(User user) {
    2. //添加一行代码,创建新的User对象
    3. user = new User();
    4. user.setAge(20);
    5. user.setName("lisi");
    6. }

    运行以上代码,你就会惊奇的发现,最终打印修改前和修改后的内容是一模一样的。 这种情况,就等同于第三种情况。因为,这里的形参和实参引用所指向的对象是不同的对象。因此,修改形参对象内容并不会影响实参内容。

    1. 修改对象前:User{age=18, name='zhangsan'}
    2. 修改对象后:User{age=18, name='zhangsan'}

    总结:
    从以上三个例子中,我们就能理解了,为什么Java中只有值传递,并没有引用传递。值传递,不论传递的参数类型是值类型还是引用类型,都会在调用栈上创建一个形参的副本。不同的是,对于值类型来说,复制的就是整个原始值的复制。而对于引用类型来说,由于在调用栈中只存储对象的引用,因此复制的只是这个引用,而不是原始对象。