Title: 图解Java参数传递
Date: 2018-11-27 12:30
Category: 技术博客
Modified: 2018-11-27 12:30
Tags: Java
Slug: JavaParaPass
Authors: Victor Lv
Summary: 先学过 C/C++ 然后再学 Java 的同学们都会有一个疑问,C/C++里面 的参数传递分为值传递、指针传递、引用传递,Java 中不存在指针,自然不存在指针传递,那么 Java 的参数传递是值传递还是引用传递?抑或是二者都有?
先学过 C/C++ 然后再学 Java 的同学们都会有一个疑问,C/C++里面 的参数传递分为值传递、指针传递、引用传递,Java 中不存在指针,自然不存在指针传递,那么 Java 的参数传递是值传递还是引用传递?抑或是二者都有?
本文将以程序示例和自拟的流程图来讲解这个问题。
程序示例:
public static void passTest1(int i) {
i = 0;
}
public static void passTest2(int[] ints) {
ints[0] = 0;
}
public static void main(String[] args) {
int i = 1;
System.out.println(i);
passTest1(i);
System.out.println(i);
int[] ints = new int[]{1,2};
System.out.println(ints[0]);
passTest2(ints);
System.out.println(ints[0]);
/**
* Output:
1
1
1
0
*/
}
可以发现传递基本类型的参数时,并没有函数外面原有变量的值;而如果传的是复杂类型(引用类型),比如数组或者 Object ,那么针对传进来的参数的内容修改,也会反映到函数外原有变量。
下面用流程图的方式描述下我对于 Java 参数传递的理解:
基本类型传参——值传递
基本类型传参流程图
对应的程序:
public static void passTest3(int i1, int i2) {
i1 = i1 + i2;
}
int i1 = 1;
int i2 = 2;
passTest3(i1, i2);
System.out.println(i1);
/**
Output:
1
*/
如图,对于基本类型参数传递,系统会将把源参数(实际参数)的内容取出来,放进(中间省略了内容传递的中间流程) CPU 缓存区,有内容则必有载体,所以这个过程中间,肯定会在一个新的内存地址中存放源参数的内容(形式参数),也就是产生了一份内容副本,但是内存地址(假设是地址B)已经和源参数(假设是地址A)的不一样了。
然后基于这两个副本的值,CPU对其做加法运算,得出一个新的值作为内容存进了地址B中。
从内容副本复制完成之后,就没**源参数(地址A)什么事了,所以自然也不会修改源参数(地址A)**里面的值了。
复杂类型(引用类型)传参——引用传递
复杂类型传参流程图
对应的程序:
public static void passTest4(int[] ints, int i) {
ints[0] = ints[0] + i;
}
int[] ints = new int[]{1,2};
int i = 6;
System.out.println(ints[0]);
passTest4(ints, i );
System.out.println(ints[0]);
/**
* Output:
1
7
*/
如图,对于复杂类型的参数传递,如果这个复杂类型的内容特别大,总不能把这么大的内容都挪出来做一个副本然后放到缓冲区吧?所以很自然地就产生了一个方法,就是:你给我一把钥匙(引用),钥匙上写着门房地址,回头我自己去这个门房地址把我要的东西取出来。
所以对于复杂类型的参数传递,传递的实际是该参数的引用(存放地址【假设是地址A】的变量),传递进来的是引用,那么函数内操作的就是实参的地址/内容了,对这个参数的取值和修改都是直接作用于地址A。比如 ints[0]
实际是通过引用去到地址A所在的堆栈空间中,取出其子元素(假设是地址A1)。在做完加法运算后,又把运算结果存到的地址A1中。于是,我们发现,地址A里面的内容已经变了(因为它里面的子地址A1的内容变了)。
所以,在复杂类型的参数传递中,有一个风险就是,我把钥匙和门房地址都给你了,那我房间的东西岂不是任你使用,并且也任你“糟蹋”(修改)?所以我们可以通过final关键字,告诉系统:你可以看但不能动我房间里面的东西,此所谓“可远观而不可亵玩焉”。