之前的 ArrayList
我已经做了一个视频放在了B站上面,其中完整提到了如何自己使用数组和泛型等技巧实现一个动态数组。链接在这里,就不额外写博客了。
所以这篇博客讲的内容并不会是具体的一个类库,而是Java语言层面的有趣设定–参数传递的方法。不同的编程语言在一些设定,比如 GC
,动/静态类型
, 传参方式
上都有所不同。而掌握这些细节无论是对于程序的优化或是AP考试都有巨大的帮助。
1 | public static void main(String[] args) { |
明眼人都看得出这段代码的意图,即将a,b两个整形变量传入一个叫做change的方法当中,实现两个数的交换(这里先想一想,可以成功吗?)。
说明这段代码之前,先明确一下定义,which我们后面会经常提到:
- 实例参数(实参):调用方法时传入的参数,即a和b,在函数体外依然有用
- 形式参数(形参):只能在函数内部使用的参数,即p和q,在函数结束后销毁(释放)
我们可以轻易的看出,在上述代码调用方法 change(a,b)
时,实例参数a和b分别将3,4这两个整形值传递给了形式参数p和q。所以我们很肯定在这次调用的函数内部中,p=3并且q=4。但是问题来了。。。
这样的参数传递究竟是以什么形式完成的呢?
接下来,我们来考虑两种传参的典型情况:值传递和引用传递。
I:引用传递
我们都知道,当我们在Java中为一个变量初始化并赋值的时候,有两个单独的步骤要走。
如上图所示:
- 我们要创建一个int类型的变量a,这一步是初始化。
- 接着我们会在内存中存放一个值3,并且a引用了这个内存的地址。
所以,当我们说 a=3 的时候,言下之意其实是变量a引用了内存中存储这个值。
那么,在我们进行参数传递时,假如(注意是假如)使用的是引用传递的方式,就会有这样的情况:
可以看到,由于形参p直接获得了a传递的引用(也就是3),而非另外在内存中创建(复制)一个值相同而地址不同的引用,a和p指向了同一个内存的地址。
So what?
那么显而易见的,如果在形参的函数体中对形参进行修改,那么影响到的将是“原数据”,毕竟这一个引用可是实参/形参共享的。如果是这种情况,最上面的 change() 的确能够实现目的。然而事实上,int 类型的参数传递遵循了下面要讲的“值传递”的方法。
II:值传递
我们来看下面这一张图:
在值传递的情况下,实参a并非直接将其引用(图中红色的3)传递给形参p。反而,该引用被复制了一份,在内存中创造了一个地址不同但是值相同的新引用(途中绿色的3)并交付给了p。这就是所谓的值传递(只传值,不传引用)。
So what?
那么在这样的情况中,对形参进行修改是否会对实参有影响呢?答案肯定是否定的。因为a和p分别有不同的引用,相应的修改也只能作用在分别引用的范围内,例子如图:
p的值被修改为4,但是不影响实参a的值保持为3。在这种的情况下,最上面的change方法无法生效。这也是实际上 java 语言设定的情况。
III:小小总结
说了这么多,那 Java 到底什么时候采用引用传递,什么时候采用值传递呢?总结如下:
值传递:基本类型(int,short,boolean…)和他们的封装类,加上String
引用传递: 其他的Object,包括基本类型的数组(int[], double[])
最后给出一个引用传递的例子(传递 char[]
参数):1
2
3
4
5
6
7
8
9
10
11public class ParameterExample
{
public static void main(String[] args) {
char[] arr = {'a','b','c'};
change(arr);
System.out.println(arr);//arr被改变,输出cbc
}
public static void change(char[] arr) {
arr[0] = 'c';//由于char[]使用引用传递,这一变化会影响实参
}
}
这也是为什么我们可以见到,在各种排序的算法当中,可以使用一个辅助的swap方法(需传入int[]以及交换的两个index)对整形数组进行改动了。
这是这个系列第3篇文章(不包括视频),简单讲解了两种不一样的传参方法。希望同学们对两种方法的不同有所了解,并熟悉他们在Java中的应用。