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 参数传递的理解:

基本类型传参——值传递

基本类型传参流程图

java将文件以请求参数传入 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)**里面的值了。

复杂类型(引用类型)传参——引用传递

复杂类型传参流程图

java将文件以请求参数传入 java怎么传参数_Java_02

对应的程序:

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关键字,告诉系统:你可以看但不能动我房间里面的东西,此所谓“可远观而不可亵玩焉”。