传值还是传引用

传值还是传引用

Java语言设计总是按值调用

按值调用:方法接收的是调用者提供的值

按引用调用:方法接收的是调用者提供的变量地址

一个方法可以修改传递引用所对应的变量值,但是不能修改传递值所对应的变量值。

基本数据类型参数 #

一个方法不能修改一个基本数据类型的参数(数值类型或布尔值)。

参考如下代码:

 1@SuppressWarnings("all")
 2class ByValue {
 3    static void tripleValue(Integer x) {
 4        x = 3 * x;
 5        System.out.println("x = " + x);
 6    }
 7
 8    public static void main(String[] args) {
 9        int y = 10;
10        // Integer y = Integer.valueOf(10);
11        y = 3 * y;
12        tripleValue(y);
13        System.out.println("y = " + y);
14    }
15}
16
17/* output:
18 * x = 30
19 * y = 10
20 */// :~

方法执行时,x被初始化为y值的一个拷贝,方法执行后 ,x值变为30,y仍为10,之后x不再使用。

对象引用 #

(引用的)对象可变 #

一个方法可以改变一个对象参数的状态

参考如下代码:

假如有一个这样的雇员类

 1public class Employee {
 2    private String name;
 3    private int salary;
 4
 5    public Employee(String name, int salary) {
 6        this.name = name;
 7        this.salary = salary;
 8    }
 9
10    public String getName() {
11        return name;
12    }
13
14    public int getSalary() {
15        return salary;
16    }
17
18    public void raiseSalary(int multiple) {
19        this.salary = salary * multiple;
20    }
21
22    @Override
23    public boolean equals(Object o) {
24        if (this == o)
25            return true;
26        if (o == null || getClass() != o.getClass())
27            return false;
28
29        Employee employee2 = (Employee) o;
30
31        return Objects.equals(name, employee2.name)
32                && salary == employee2.salary;
33    }
34
35    @Override
36    public int hashCode() {
37        int result = name != null ? name.hashCode() : 0;
38        result = 31 * result + salary;
39        return result;
40    }
41}

我们要给雇员涨薪水了

 1public class ByRef {
 2
 3    static void raiseSalary(Employee o) {
 4        o.raiseSalary(3);
 5        System.out.println("after salary of o = " + o.getSalary());
 6    }
 7
 8    static void swap(Employee j, Employee k) {
 9        System.out.println("before swap j.name = " + j.getName());
10        System.out.println("before swap k.name = " + k.getName());
11        Employee temp = j;
12        j = k;
13        k = temp;
14        System.out.println("after swap j.name = " + j.getName());
15        System.out.println("after swap k.name = " + k.getName());
16    }
17
18    public static void main(String[] args) {
19        Employee a = new Employee("ali", 1200);
20        System.out.println("before salary = " + a.getSalary());
21        raiseSalary(a);
22        System.out.println("after salary of a = " + a.getSalary());
23        Employee b = new Employee("bad", 1300);
24        swap(a, b);
25        System.out.println("after swap a.name = " + a.getName());
26        System.out.println("after swap b.name = " + b.getName());
27    }
28}
29
30
31/*
32 * before salary = 1200
33 * after salary of o = 3600
34 * after salary of a = 3600
35 */// :~

方法执行时,o被初始化为a值的拷贝,其中a的值为对象的引用;raiseSalary方法作用与oa同时引用的那个对象;方法结束后,o对象不再使用,结果是a对象的薪水涨至原来3倍

也就是说,当对象引用作为参数时,可以对原对象的属性(域)进行修改。

既然如此,那是否可以将 基本类型参数转化为其包装类型呢,这样就可以实现小节1中的期望了:

 1// doesn't work
 2class ByValue {
 3    static void tripleValue(Integer x) {
 4        x = 3 * x;
 5        System.out.println("x = " + x);
 6    }
 7
 8    public static void main(String[] args) {
 9        Integer y = Integer.valueOf(10);
10        tripleValue(y);
11        System.out.println("y = " + y);
12    }
13}
14/* output:
15x = 30
16y = 10
17*///:~

非常遗憾,失败了。这是因为基础数据类型的包装器类型是final的,其value域(包装类型的值)也是final的。

当使用Integer y = 10;初始化y时,Java会将y自动拆箱为int;

同样地,x = 3 * x;对Integer类型作操作符运算也会拆箱。

引用不可变 #

一个方法不能让对象参数引用一个新的对象

不妨再看看 上述示例代码中的swap()方法:

swap()方法用于交换两个对象的引用。我们得到如下输出:

1/* output:
2before swap j.name = ali
3before swap k.name = bad
4after swap j.name = bad
5after swap k.name = ali
6after swap a.name = ali
7after swap b.name = bad
8*///:~

方法执行时,jk分别是ab值的拷贝,也就是说jk分别是ab的对象引用,swap方法交换了这两个拷贝,但是方法结束后,jk不再使用,而ab还是指向方法调用前所指向的对象。

也就是说,对于对象参数,对象参数的引用也是按值传递的

如何理解这句话?

  1. 引用数据类型的参数传递实际上是对象的一个引用(按引用传递)

  2. 被传递的这个引用本身是值传递性质(引用无法被方法更改)