浅谈 Java 中的参数传递

文章目录
  1. 1. 前言概述
  2. 2. 扩展详谈
    1. 2.1. 基本数据类型
    2. 2.2. 引用类型

莫忘初衷 / 莫忘初衷 / 别忘 / 那一年 / 那一天 / 出发时心中的梦

引言来自动力火车的一首老歌《莫忘初衷》,确实,保持初心并不容易,走得太远了,不要忘记当初为何要出发!

前言概述

前段时间,重构某块代码时踩到了一个坑,就是混淆了 Java 中的值传递与引用传递,导致某块属性没有生效,这次扩展谈一下。

Java 的参数传递究竟是传值还是传引用呢?换句话说,当一个对象被当作参数传递到一个方法后,该方法内可以改变这个对象的属性,这里究竟是按值传递还是按引用传递?

按值传递!网上大多将 Java 的参数传递分为按值传递和按引用传递,这里,我认为只有按值传递。

  • 当一个实例对象被作为参数传递到某个方法中时,参数值事实上为该对象的引用的一个副本
  • 当指向同一个对象时,对象的内容在被调用的方法内改变,然而,对象的引用 (这里的引用不是引用的副本) 是永不改变的

事实上,微软的文档有段按引用传递参数的说明如下:

在方法的参数列表中使用ref关键字时,它指示参数按引用传递,而非按值传递。按引用传递的效果是,对所调用方法中参数进行的任何更改都反映在调用方法中。例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象。

注意!不要混淆通过引用传递的概念与引用类型的概念。这两种概念是不同的。无论方法参数是值类型还是引用类型,均可由 ref 修改。当通过引用传递时,不会对值类型装箱。

扩展详谈

下面将参数分为基本数据类型和引用类型参数分开说。

基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BasicParams {

public static void main(String[] args) {
int num = 3;
System.out.println("调用 convertNum() 方法前: num = " + num);
convertNum(num);
System.out.println("调用 convertNum() 方法后: num = " + num);
}

private static void convertNum(int i) {
i = 6;
}
}

结果如下:

1
2
调用 convertNum() 方法前: num = 3
调用 convertNum() 方法后: num = 3

我们发现,值并没有改变。来看一个简单的示意图:

num 作为参数传递给 convertNum() 时,是将内存空间中 num 指向的存储单元中存放的值 3 传递给 convertNum() 中的 i 变量,然而,该 i 变量也在内存空间中分配了一个存储单元,即这时将 num 的值 3 传递给了 i 的这个存储单元。这以后,在 convertNum() 中对 i 的一切操作都是针对 i 所指向的这个存储单元,与 num 所指向的那个存储单元不再有任何关系。

因此,调用 convertNum() 后,num 所指向的存储单元的值未发生变化。

引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ReferParams {

public static void main(String[] args) {
Reference r1 = new Reference();
System.out.println("调用 convert() 方法前: r1 = " + r1);
convert(r1);
System.out.println("调用 convert() 方法后: r1 = " + r1);
}

private static void convert(Reference r2) {
r2 = new Reference();
}

static class Reference {

}
}

结果如下:

1
2
调用 convert() 方法前: r1 = [email protected]
调用 convert() 方法后: r1 = [email protected]

显然,打印两次的 Reference 地址的值是一样的,换句话说,调用 convert() 方法后,Reference 类型的变量并没有发生改变。简单的示意图如下:

程序执行到Reference r1 = new Reference()时,在堆内存中开辟了一块内存空间,用来存储 Reference 类的实例对象,同时,在栈内存中开辟一个存储单元,来存储该实例对象的引用,即图中 p1 指向的存储单元。

执行到convert()时,p1 作为参数传递给该方法,即p1 将自己存储单元的内容传递给了 convert() 方法的 p2 变量,之后,convert() 方法中对 p2 的一切操作,都是针对 p2 指向的存储单元,与 p1 所指向的那个存储单元就没有关系了。

但是,若改造成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ReferParams {

public static void main(String[] args) {
Reference r1 = new Reference();
r1.setValue(1);
System.out.println("调用 convert() 方法前: r1 = " + r1.getValue());
convert(r1);
System.out.println("调用 convert() 方法后: r1 = " + r1.getValue());
}

private static void convert(Reference r2) {
r2.setValue(2);
}

static class Reference {
private int mValue;

public int getValue() {
return mValue;
}

public void setValue(int mValue) {
this.mValue = mValue;
}
}
}

结果如下:

1
2
调用 convert() 方法前: r1 = 1
调用 convert() 方法后: r1 = 2

形参和实参指向同一个内存地址,公用一个内存,会影响到调用方。

至此,关于浅谈 Java 中的参数传递完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流