1、实参和形参

  在解释什么是值传递和地址传递前,我们先引入两个概念:实参和形参。

  实参:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”,简称“实参”。

  形参:在定义函数名和函数体的时候使用的参数称为“形式参数”,简称“形参”,目的是用来接收调用该函数时传入的参数。

在代码中:
public class Main {

    public static void main(String[] args) {
        int a = 1;
	// 实参
        passByValue(a);
    }
    // 形参
    public static void transmit(int a) {
    }
}

2、什么是值传递

  值传递是指在调用函数(方法)时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

例如

  我们在 main 方法中创建一个变量 a,然后调用 passByValue 方法修改变量 a 的值。

public class Main {

    public static void main(String[] args) {
        int a = 1;
        System.out.println("调用前" + a);
        passByValue(a);
        System.out.println("调用后" + a);
    }

    public static void passByValue(int a) {
        // 改变a的值
        a = a + 1;
        System.out.println("方法中" + a);
    }
}

控制台的打印结果

调用前1
方法中2
调用后1

  我们发现 main 方法中 a 变量的值并没有因为调用 passByValue 方法而发生改变。因为在调用方法时是将实际参数复制一份传递到方法中,main 方法中的 a 和 passByValue 方法中的 a 不在一个内存地址中,所以在方法中修改形式参数的值,并不会影响到实际参数的值。

3、什么是地址传递

  地址传递也叫引用传递是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

例如

  我们自定义一个 User 类,然后在 main 方法中创建该类的实例并赋初值,接着调用 addressPassing 方法修改 User 类中的值。

public class Main {

    public static void main(String[] args) {
        User user = new User();
        user.id = 1;
        user.name = "Yi-Xing";
        System.out.println("调用前" + user);
        addressPassing(user);
        System.out.println("调用后" + user);
    }

    public static void addressPassing(User user) {
        // 改变user的值
        user.id = 2;
        user.name = "zyxwmj.top";
        System.out.println("方法中" + user);
    }

    static class User {
        int id;
        String name;

        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
}

控制台的打印结果

调用前User{id=1, name='Yi-Xing'}
方法中User{id=2, name='zyxwmj.top'}
调用后User{id=2, name='zyxwmj.top'}

  我们发现 main 方法中 user 对象的值因为调用 addressPassing 方法而发生改变。因为在调用方法时是将实际参数的内存地址传递给了形参,main 方法中的 user 和 passByValue 方法中的 user 指向了同一块内存地址,所以在方法中修改形式参数的值,会影响到实际参数的值。

  如果我们不重写 User 类的 toString 方法,Java 则会将 user 的内存地址打印在控制台上,运行结果如下:

调用前blog.Main$User@1f32e575
方法中blog.Main$User@1f32e575
调用后blog.Main$User@1f32e575

  正如我们所想 main 方法中的 user 和 addressPassing 方法中的 user 是指向同一块内存地址。

重点

  但有人提出质疑了,并搬出一下代码。

public class Main {

    public static void main(String[] args) {
        User user = new User();
        user.id = 1;
        user.name = "Yi-Xing";
        System.out.println("调用前" + user);
        addressPassing(user);
        System.out.println("调用后" + user);
    }

    public static void addressPassing(User user) {
        user = new User();
        // 改变user的值
        user.id = 2;
        user.name = "zyxwmj.top";
        System.out.println("方法中" + user);
    }

    static class User {
        int id;
        String name;

        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
}

控制台的打印结果

调用前User{id=1, name='Yi-Xing'}
方法中User{id=2, name='zyxwmj.top'}
调用后User{id=1, name='Yi-Xing'}

  不难发现,在 addressPassing 方法中修改了 user 的值并没有影响到 main 中的 user。实际上在方法调用时, addressPassing 方法中的 user 和 main 方法中的 user 指向同一块内存,只不过我们在 addressPassing 方法中又从新对 user 进行了赋值更改了指向的内存地址,赋值后的 user 和 main 方法中的 user 指向不同的内存地址。具体代码如下:

public class Main {

    public static void main(String[] args) {
        User user = new User();
        user.id = 1;
        user.name = "Yi-Xing";
        System.out.println("调用前" + user);
        addressPassing(user);
        System.out.println("调用后" + user);
    }

    public static void addressPassing(User user) {
        System.out.println("赋值前" + user);
        user = new User();
        System.out.println("赋值后" + user);
        // 改变user的值
        user.id = 2;
        user.name = "zyxwmj.top";
        System.out.println("方法中" + user);
    }

    static class User {
        int id;
        String name;
    }
}

控制台的打印结果

调用前blog.Main$User@1f32e575
赋值前blog.Main$User@1f32e575
赋值后blog.Main$User@279f2327
方法中blog.Main$User@279f2327
调用后blog.Main$User@1f32e575

  运行结果正如我们所想,赋值前、调用前、调用后的 user 指向同一块内存地址。

对象不可变性
public class Main {

    public static void main(String[] args) {
        String str = "hello";
        Integer num = 2;
        Float f = 2f;
        change(str, num,f);
        System.out.println("str = " + str);
        System.out.println("num = " + num);
        System.out.println("f = " + f);
    }

    public static void change(String s, Integer n,Float f) {
        s += "world";
        n += 1;
        f = 3f;
    }
}

  以上代码的运行结果为:

str = hello
num = 2
f = 2.0

  String,Integer,Float 这些对象都是引用类型,方法传参时,传递的是内存地址,为什么在 change 方法内更改了变量值后,main 方法中的变量没有受影响呢?

  因为 String 和 Integer 等包装类都具有对象不可变性,对象值在进行改变时,并不是在原内存上进行修改值,而是创建新的内存空间,让变量指针指向新的内存空间。

  change 方法中的变量指向了新的内存空间,而 main 方法中的变量还指向原内存空间,所以值还是原来的值。

4、结论

  • 形参是基本数据类型:
    • 传递数据值。
  • 形参是引用数据类型:
    • 传递地址值;
    • 特殊的类型:String、包装类等对象不可变性。
public class Exam4 {

    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 2;
        int[] arr = {1, 2, 3, 4, 5};
        MyData my = new MyData();
        change(i, str, num, arr, my);
        System.out.println("i = " + i);
        System.out.println("str = " + str);
        System.out.println("num = " + num);
        System.out.println("arr = " + Arrays.toString(arr));
        System.out.println("my.a = " + my.a);
    }

    public static void change(int j, String s, Integer n, int[] a, MyData m) {
        j += 1;
        s += "world";
        n += 1;
        a[0] += 1;
        m.a += 1;
    }
}

class MyData {
    int a = 10;
}

  运行结果:

i = 1
str = hello
num = 2
arr = [2, 2, 3, 4, 5]
my.a = 11

标题:Java 中方法的参数传递机制
作者:Yi-Xing
地址:http://47.94.239.232:10014/articles/2020/02/29/1582977758281.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!