Java中的参数传递类型

在阅读HashMap中的源码过程中,阅读到一些方法之间的调用涉及到参数传递,有些情况下感觉最后的结果和我想的不一样,所以特地了解了一下参数传递,在这里也记录一下,防止自己忘记。


文章目录

  • Java中的参数传递类型
  • 了解一些基础概念
  • 形式参数和实际参数
  • 形参和实参的举例
  • 值传递和引用传递
  • 值传递和引用传递的举例
  • 了解Java中的传递类型
  • 对于传递类型的解析
  • 举一些小例子


了解一些基础概念

在学习参数传递之前需要先了解一些基础概念,这里也会给出实例代码。方便理解。

形式参数和实际参数

参数传递在程序中是比较常见的。参数传递涉及到两个概念。形式参数和实际参数。下面就说一下两者的区别。

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数,简称“形参”。
实际参数:在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”,简称“实参”。

Java也不例外,也存在形式参数和实际参数,在这里分别举个例子。

形参和实参的举例

形式参数:

public static void test(StringBuffer s, int a)//形式参数s和a

实际参数:

StringBuffer s = new StringBuffer("main");
int a = 1;
test(s, a);//实际参数s和a

看了上面的例子,是不是了解了形式参数和实际参数的区别。

值传递和引用传递

在程序语言中的参数传递类型有两种,分别为值传递和引用传递。两者的概念和区别如下:

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

这里的举例用C来举例,用C的代码分别举例值传递和引用传递。

值传递和引用传递的举例

值传递:

#include <stdio.h>
void swap(int a,int b);

void main(){
    int a = 3;
    int b = 4;
    printf("bef swap, add of a = %d\n",&a);
    printf("aft swap, val of a = %d\n",a);
    swap(a,b);
    printf("aft swap, add of a = %d\n",&a);
    printf("aft swap, val of a = %d\n",a);
}
// pass by value
void swap(int a,int b){
    int temp = a;
    a = b;
    b = temp;
}

引用传递:

#include <stdio.h>
void swap2(int *a,int *b);

void main(){
    int a = 3;
    int b = 4;
    printf("bef swap, add of a = %d\n",&a);
    printf("aft swap, val of a = %d\n",a);
    swap2(&a,&b);
    printf("aft swap, add of a = %d\n",&a);
    printf("aft swap, val of a = %d\n",a);
}

// pass by reference
void swap2(int *a,int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

上面两个例子就是值传递和引用传递的区别,运行后也是不同的效果,还是比较明显的。c这一块不是很熟悉,代码可能有误,但是思想我认为传达到了。

了解Java中的传递类型

终于到了本文的正题了,开始讲解一下Java中的参数传递的机制和原理。在讲解之前,需要先了解一下JVM中的堆和栈的区别。

  • 栈内存。栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
  • 堆内存。存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。

可以简单的理解为方法中的变量都存储在栈中,实际的对象都存储在堆中。方法之间是不能够互相修改变量的。所以也就有了这种说法。Java中只存在值传递,不存在引用传递,因为本质上都是复制了一份副本,所以都是值传递。下面就开始讲解。

对于传递类型的解析

Java中的参数分为两种,基本类型参数和引用数据类型,基本数据类型基本上没有什么分歧,都认为是值传递。主要分歧都在引用数据类型上。

在方法之间传递一个引用数据类型是,类似于这种foo(User user),变量user相当于一个指针,指向了堆中实际的存储对象。在传递过程中,将指针进行复制,传递到foo方法中,两个变量是有区别的,但是指想的都是堆中同一个对象。

说的可能不太好理解,举一些例子来说明一下。

举一些小例子

  • 例子一:
package nativetrain;

/**
 * TransferHKTrain.java
 * Description: 调用海康SDK的练习
 *
 * @author Peng Shiquan
 * @date 2021/3/9
 */
public class TransferHKTrain {

    public static void main(String[] args) {
        User user = new User("main");
        test(user);
        System.err.println(user.name);
    }

    public static void test(User user) {
        user.name = "test";
        System.err.println(user.name);
    }
}

class User {
    public String name;

    User(String name) {
        this.name = name;
    }
}

运行结果如下图:

java中的实际参数 java中参数是什么_java

这个运行结果好像和我们说的不一致,其实细想一下就明白了,方法传递的是一个指针的副本,最后指向的都是堆中的对象。修改这个对象后在主方法中当然可以起效。但是和引用传递还是有区别的,区别就是引用传递能修改真正的参数,但是Java中的传递并不行。可以再看一下下面一个代码示例。

  • 例子二:
package nativetrain;

/**
 * TransferHKTrain.java
 * Description: 调用海康SDK的练习
 *
 * @author Peng Shiquan
 * @date 2021/3/9
 */
public class TransferHKTrain {

    public static void main(String[] args) {
        User user = new User("main");
        test(user);
        System.err.println(user.name);
    }

    public static void test(User user) {
        user = new User("test");
        System.err.println(user.name);
    }
}

class User {
    public String name;

    User(String name) {
        this.name = name;
    }
}

截图如下:

java中的实际参数 java中参数是什么_User_02

只是简单的修改了一下,最后的结果就不一样了。这里将参数传递的变量进行初始化,相当于在堆中又创建了一个User对象,和主方法中的user对象所指向的对象是两个完全不同的对象,所以这里的修改也就没有起效。两次的打印也就不一样。

到这里,是不是对于Java中参数传递类型就更加清楚了一些。能力有限,如果文中有些错误,欢迎大佬指正。

本文也借鉴了一些博客,博客地址:深入理解Java中方法的参数传递机制

就这样吧,结束。