一个例子彻底讲透Java参数传递方式

176 阅读4分钟

介绍

总是搞不清楚参数传来传去到底是否会改变原有参数的值?本文将通过一个例子彻底讲清java的值传递到底是怎么玩的。

在程序设计语言中参数传递给方法有两种方式:

  • 按值调用:表示方法接收的是调用者提供的值
  • 按引用调用:表示方法接收的是调用者提供的变量地址,(这个变量地址可以抽象为数据实际存放的位置)

而在Java中总是采用按值调用的方式进行参数传递。也就是说方法接收到的参数是调用者提供参数值的一个拷贝,现在不清楚没关系,我们接着往下看。

分析

要弄清楚参数传来传去到底是否会改变原有参数的值,在这里我主要分为两种种情况来分别探讨

  1. 基本数据类型参数传递
  2. 对象引用作为参数传递

基本数据类型的参数传递

Java语言使用的是按值调用方式进行参数传递,因此在方法中是不能修改传递给它的任何参数的值。例如:将percent增加3倍的一段程序

public static void main(String[] args){
      /*
       * Test 1: Methods can't modify numeric parameters
       */
      System.out.println("Testing tripleValue:");
      double percent = 10;
      System.out.println("Before: percent=" + percent);
      tripleValue(percent);
      System.out.println("After: percent=" + percent);
   }
​
   public static void tripleValue(double x) // doesn't work
   {
      x = 3 * x;
      System.out.println("End of method: x=" + x);
   }

这里percent在调用tripleValue方法前后的变化如下:

image-20240807100544383

可以看到,调用了tripleValue方法后。percent的值并没有发生改变,只有tripleValue方法中的x增加了3倍。

这是因为这一段代码的执行流程是这样的:

  1. 调用tripleValue(percent)方法时,x会被初始化为percent值的一个拷贝值,这个拷贝值也等于10
  2. tripleValue(double x)方法中,x被乘以3后等于30了,但是percent的值并没有发生改变

image-20240807103138324

  1. 方法结束后,参数变量x就不再被使用了

对象引用作为参数传递

但是对象引用作为参数就不同了,可以看下面一段程序,将一个雇员的薪资提高200%操作:

class Employee // simplified Employee class
{
    private String name;
    private double salary;
​
    public Employee(String n, double s)
    {
        name = n;
        salary = s;
    }
​
    public String getName()
    {
        return name;
    }
​
    public double getSalary()
    {
        return salary;
    }
​
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
}
​
public static void main(String[] args)
{
​
    /*
       * Test 2: Methods can change the state of object parameters
       */
    System.out.println("\nTesting tripleSalary:");
    Employee harry = new Employee("Harry", 50000);
    System.out.println("Before: salary=" + harry.getSalary());
    tripleSalary(harry);
    System.out.println("After: salary=" + harry.getSalary());
​
}
​
public static void tripleSalary(Employee x) // works
{
    x.raiseSalary(200);
    System.out.println("End of method: salary=" + x.getSalary());
}

这段代码输出结果为:

image-20240807103625930

此时具体执行过程为:

  1. 调用tripleSalary(harry)方法时,x被初始化为harry值的拷贝,注意这里harry值是Employee对象的引用,也就是说harry值的拷贝是当前对象的地址值,此时x和harry同时引用了同一个Employee对象。
  2. tripleSalary方法中x.raiseSalary(200)的这个操作实际上是调用的同一个Employee对象方法。
  3. 方法结束后x不再使用,但由于x和harry引用的同一个对象,因此x修改被保留了下来。

image-20240807105216662

看到这里,如果你了解C++的话,你可能困惑这不就是C++中的按引用传递的方式吗?难道Java针对对象使用的也是按引用传递的方式?其实不然,最后在通过一个程序片段彻底理解Java的按值传递方式。下面这段代码是用于交换两个雇员对象

public class Test{
    public static void swap(Employee x, Employee y)
    {
        Employee temp = x;
        x = y;
        y = temp;
        System.out.println("End of method: x=" + x.getName());
        System.out.println("End of method: y=" + y.getName());
    }
​
    public static void main(String[] args)
    {
        /*
           * Test 3: Methods can't attach new objects to object parameters
           */
        System.out.println("\nTesting swap:");
        Employee a = new Employee("Alice", 70000);
        Employee b = new Employee("Bob", 60000);
        System.out.println("Before: a=" + a.getName());
        System.out.println("Before: b=" + b.getName());
        swap(a, b);
        System.out.println("After: a=" + a.getName());
        System.out.println("After: b=" + b.getName());
    }
}
class Employee // simplified Employee class
{
    private String name;
    private double salary;
​
    public Employee(String n, double s)
    {
        name = n;
        salary = s;
    }
​
    public String getName()
    {
        return name;
    }
​
    public double getSalary()
    {
        return salary;
    }
​
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
}

如果Java对象传递的方式是按引用传递那么swap方法出来后的结果会是a和b交换引用,但实际结果却是这样的:

image-20240807105926069

从结果可以看到,只有方法内的x和y交换了,实际的a和b并没有交换。也就是说swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

image-20240807111251575

总结

  • Java是按值传递的
  • 基本数据类型在方法中的计算结果不会被保留,需要设计额外的结果保留方式。(如使用成员变量保存结果等)
  • 对象引用不能在方法中进行交换,方法中交换的是对象引用的拷贝。